Skip to content

Commit aa08843

Browse files
authored
[libc++] Avoid -Wzero-as-null-pointer-constant in operator<=> (#79465)
Issue #43670 describes a situation where the following comparison will issue a warning when -Wzero-as-null-pointer-constant is enabled: #include <compare> auto b = (1 <=> 2) < 0; This code uses operator<(strong_ordering, Unspecified), which is specified by the Standard to only work with a literal 0. In the library, this is achieved by constructing Unspecified from a pointer, which works but has the downside of triggering the warning. This patch uses an alternative implementation where we require that the operator is used exactly with an int of value 0 (known at compile-time), however that value can technically be an expression like `1 - 1`, which makes us a bit less strict than what's specified in the Standard. Fixes #43670
1 parent 4f14bfe commit aa08843

File tree

3 files changed

+89
-65
lines changed

3 files changed

+89
-65
lines changed

libcxx/include/__compare/ordering.h

Lines changed: 15 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -30,14 +30,20 @@ class partial_ordering;
3030
class weak_ordering;
3131
class strong_ordering;
3232

33-
template <class _Tp, class... _Args>
34-
inline constexpr bool __one_of_v = (is_same_v<_Tp, _Args> || ...);
35-
3633
struct _CmpUnspecifiedParam {
37-
_LIBCPP_HIDE_FROM_ABI constexpr _CmpUnspecifiedParam(int _CmpUnspecifiedParam::*) noexcept {}
38-
39-
template <class _Tp, class = enable_if_t<!__one_of_v<_Tp, int, partial_ordering, weak_ordering, strong_ordering>>>
40-
_CmpUnspecifiedParam(_Tp) = delete;
34+
// If anything other than a literal 0 is provided, the behavior is undefined by the Standard.
35+
//
36+
// The alternative to the `__enable_if__` attribute would be to use the fact that a pointer
37+
// can be constructed from literal 0, but this conflicts with `-Wzero-as-null-pointer-constant`.
38+
template <class _Tp, class = __enable_if_t<is_same_v<_Tp, int> > >
39+
_LIBCPP_HIDE_FROM_ABI consteval _CmpUnspecifiedParam(_Tp __zero) noexcept
40+
# if __has_attribute(__enable_if__)
41+
__attribute__((__enable_if__(
42+
__zero == 0, "Only literal 0 is allowed as the operand of a comparison with one of the ordering types")))
43+
# endif
44+
{
45+
(void)__zero;
46+
}
4147
};
4248

4349
class partial_ordering {
@@ -269,7 +275,8 @@ inline constexpr strong_ordering strong_ordering::greater(_OrdResult::__greater)
269275
/// The types partial_ordering, weak_ordering, and strong_ordering are
270276
/// collectively termed the comparison category types.
271277
template <class _Tp>
272-
concept __comparison_category = __one_of_v<_Tp, partial_ordering, weak_ordering, strong_ordering>;
278+
concept __comparison_category =
279+
is_same_v<_Tp, partial_ordering> || is_same_v<_Tp, weak_ordering> || is_same_v<_Tp, strong_ordering>;
273280

274281
#endif // _LIBCPP_STD_VER >= 20
275282

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4+
// See https://llvm.org/LICENSE.txt for license information.
5+
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6+
//
7+
//===----------------------------------------------------------------------===//
8+
9+
// UNSUPPORTED: c++03, c++11, c++14, c++17
10+
11+
// <compare>
12+
13+
// Ensure we reject all cases where an argument other than a literal 0 is used
14+
// for a comparison against a comparison category type.
15+
16+
// Also ensure that we don't warn about providing a null pointer constant when
17+
// comparing an ordering type against literal 0, since one of the common
18+
// implementation strategies is to use a pointer as the "unspecified type".
19+
// ADDITIONAL_COMPILE_FLAGS: -Wzero-as-null-pointer-constant
20+
21+
#include <compare>
22+
23+
#include "test_macros.h"
24+
25+
#define TEST_FAIL(v, op) \
26+
do { \
27+
/* invalid types */ \
28+
void(v op 0L); \
29+
void(0L op v); \
30+
void(v op 0.0); \
31+
void(0.0 op v); \
32+
void(v op nullptr); \
33+
void(nullptr op v); \
34+
/* invalid value */ \
35+
void(v op 1); \
36+
void(1 op v); \
37+
/* value not known at compile-time */ \
38+
int i = 0; \
39+
void(v op i); \
40+
void(i op v); \
41+
} while (false)
42+
43+
#define TEST_PASS(v, op) \
44+
do { \
45+
void(v op 0); \
46+
void(0 op v); \
47+
LIBCPP_ONLY(void(v op(1 - 1))); \
48+
LIBCPP_ONLY(void((1 - 1) op v)); \
49+
} while (false)
50+
51+
template <typename T>
52+
void test_category(T v) {
53+
TEST_FAIL(v, ==); // expected-error 30 {{invalid operands to binary expression}}
54+
TEST_FAIL(v, !=); // expected-error 30 {{invalid operands to binary expression}}
55+
TEST_FAIL(v, <); // expected-error 30 {{invalid operands to binary expression}}
56+
TEST_FAIL(v, <=); // expected-error 30 {{invalid operands to binary expression}}
57+
TEST_FAIL(v, >); // expected-error 30 {{invalid operands to binary expression}}
58+
TEST_FAIL(v, >=); // expected-error 30 {{invalid operands to binary expression}}
59+
TEST_FAIL(v, <=>); // expected-error 30 {{invalid operands to binary expression}}
60+
61+
TEST_PASS(v, ==);
62+
TEST_PASS(v, !=);
63+
TEST_PASS(v, <);
64+
TEST_PASS(v, >);
65+
TEST_PASS(v, <=);
66+
TEST_PASS(v, >=);
67+
TEST_PASS(v, <=>);
68+
}
69+
70+
void f() {
71+
test_category(std::strong_ordering::equivalent);
72+
test_category(std::weak_ordering::equivalent);
73+
test_category(std::partial_ordering::equivalent);
74+
}

libcxx/test/std/language.support/cmp/cmp.categories.pre/zero_type.verify.cpp

Lines changed: 0 additions & 57 deletions
This file was deleted.

0 commit comments

Comments
 (0)