[libcxx-commits] [libcxx] 97bb7ec - [libc++] Reject cv-qualified types in __is_signed/unsigned_integer_v (#200377)
via libcxx-commits
libcxx-commits at lists.llvm.org
Wed Jun 3 01:04:58 PDT 2026
Author: Xavier Roche
Date: 2026-06-03T10:04:54+02:00
New Revision: 97bb7ec082b675cabae8c9e5001a8a8c14d03731
URL: https://github.com/llvm/llvm-project/commit/97bb7ec082b675cabae8c9e5001a8a8c14d03731
DIFF: https://github.com/llvm/llvm-project/commit/97bb7ec082b675cabae8c9e5001a8a8c14d03731.diff
LOG: [libc++] Reject cv-qualified types in __is_signed/unsigned_integer_v (#200377)
`__is_signed_integer_v` and `__is_unsigned_integer_v` previously
admitted cv-qualified types, contradicting `[basic.fundamental]/p1-2`.
The fix adds `__is_unqualified_v<_Tp>` to both predicates.
Reported by @jwakely on
[#185027](https://github.com/llvm/llvm-project/pull/185027#issuecomment-4574289895).
Trait-level tests cover all consumers via the shared concept; cast-like
templates (`in_range`, `saturating_cast`, `extents`) get explicit
cv-rejection tests.
Assisted-by: Claude (Anthropic)
---------
Co-authored-by: Claude Opus 4.6 <noreply at anthropic.com>
Added:
Modified:
libcxx/include/__type_traits/integer_traits.h
libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_signed_integer.compile.pass.cpp
libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_unsigned_integer.compile.pass.cpp
libcxx/test/std/containers/views/mdspan/extents/index_type.verify.cpp
libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_cast.compile.pass.cpp
libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
Removed:
################################################################################
diff --git a/libcxx/include/__type_traits/integer_traits.h b/libcxx/include/__type_traits/integer_traits.h
index d7ac89be9c2a7..a0fbcd5c3ecd7 100644
--- a/libcxx/include/__type_traits/integer_traits.h
+++ b/libcxx/include/__type_traits/integer_traits.h
@@ -13,6 +13,7 @@
#include <__type_traits/is_integral.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_signed.h>
+#include <__type_traits/is_unqualified.h>
#include <__type_traits/is_unsigned.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
@@ -21,11 +22,10 @@
_LIBCPP_BEGIN_NAMESPACE_STD
-// These traits determine whether a type is a /signed integer type/ or
-// /unsigned integer type/ per [basic.fundamental]/p1-2.
-//
-// Character types (char, wchar_t, char8_t, char16_t, char32_t) and bool
-// are integral but are NOT signed/unsigned integer types.
+// /signed integer type/ and /unsigned integer type/ per [basic.fundamental]
+// /p1-2: specific unqualified types plus extended integer types. bool and
+// character types are integral but excluded. cv-qualified versions are
+// distinct types ([basic.type.qualifier]) and so excluded.
template <class _Tp>
inline const bool __is_character_v = false;
@@ -44,11 +44,13 @@ inline const bool __is_character_v<char32_t> = true;
template <class _Tp>
inline const bool __is_signed_integer_v =
- is_integral<_Tp>::value && is_signed<_Tp>::value && !__is_character_v<_Tp> && !is_same<_Tp, bool>::value;
+ is_integral<_Tp>::value && is_signed<_Tp>::value && !__is_character_v<_Tp> && !is_same<_Tp, bool>::value &&
+ __is_unqualified_v<_Tp>;
template <class _Tp>
inline const bool __is_unsigned_integer_v =
- is_integral<_Tp>::value && is_unsigned<_Tp>::value && !__is_character_v<_Tp> && !is_same<_Tp, bool>::value;
+ is_integral<_Tp>::value && is_unsigned<_Tp>::value && !__is_character_v<_Tp> && !is_same<_Tp, bool>::value &&
+ __is_unqualified_v<_Tp>;
#if _LIBCPP_STD_VER >= 20
template <class _Tp>
diff --git a/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_signed_integer.compile.pass.cpp b/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_signed_integer.compile.pass.cpp
index 3fa342685770c..1f2d9685bbe5a 100644
--- a/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_signed_integer.compile.pass.cpp
+++ b/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_signed_integer.compile.pass.cpp
@@ -61,3 +61,35 @@ static_assert(!std::__signed_integer<unsigned int*>);
static_assert(!std::__signed_integer<SomeObject>);
static_assert(!std::__signed_integer<SomeEnum>);
static_assert(!std::__signed_integer<SomeScopedEnum>);
+
+// cv-qualified versions are distinct types ([basic.type.qualifier]) and so
+// not signed integer types per [basic.fundamental]/p1. The three meaningful
+// flavors in C++ are const, volatile, and const volatile.
+static_assert(!std::__signed_integer<const int>);
+static_assert(!std::__signed_integer<volatile int>);
+static_assert(!std::__signed_integer<const volatile int>);
+static_assert(!std::__signed_integer<const long long>);
+static_assert(!std::__signed_integer<const signed char>);
+static_assert(!std::__signed_integer<const char>);
+static_assert(!std::__signed_integer<const bool>);
+#ifndef TEST_HAS_NO_WIDE_CHARACTERS
+static_assert(!std::__signed_integer<const wchar_t>);
+#endif
+static_assert(!std::__signed_integer<int&>);
+static_assert(!std::__signed_integer<const int&>);
+
+// Extended signed integer types per [basic.fundamental]/p3 Note 1.
+#if TEST_HAS_EXTENSION(bit_int)
+static_assert(std::__signed_integer<signed _BitInt(8)>);
+static_assert(std::__signed_integer<signed _BitInt(16)>);
+static_assert(std::__signed_integer<signed _BitInt(64)>);
+static_assert(std::__signed_integer<signed _BitInt(13)>);
+static_assert(!std::__signed_integer<unsigned _BitInt(16)>);
+static_assert(!std::__signed_integer<const signed _BitInt(16)>);
+static_assert(!std::__signed_integer<volatile signed _BitInt(64)>);
+static_assert(!std::__signed_integer<const volatile signed _BitInt(13)>);
+# if __BITINT_MAXWIDTH__ >= 128
+static_assert(std::__signed_integer<signed _BitInt(128)>);
+static_assert(!std::__signed_integer<const signed _BitInt(128)>);
+# endif
+#endif
diff --git a/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_unsigned_integer.compile.pass.cpp b/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_unsigned_integer.compile.pass.cpp
index ff60f32319171..3f78f170b7038 100644
--- a/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_unsigned_integer.compile.pass.cpp
+++ b/libcxx/test/libcxx/concepts/concepts.arithmetic/__libcpp_unsigned_integer.compile.pass.cpp
@@ -61,3 +61,35 @@ static_assert(!std::__unsigned_integer<unsigned int*>);
static_assert(!std::__unsigned_integer<SomeObject>);
static_assert(!std::__unsigned_integer<SomeEnum>);
static_assert(!std::__unsigned_integer<SomeScopedEnum>);
+
+// cv-qualified versions are distinct types ([basic.type.qualifier]) and so
+// not unsigned integer types per [basic.fundamental]/p2. The three meaningful
+// flavors in C++ are const, volatile, and const volatile.
+static_assert(!std::__unsigned_integer<const unsigned int>);
+static_assert(!std::__unsigned_integer<volatile unsigned int>);
+static_assert(!std::__unsigned_integer<const volatile unsigned int>);
+static_assert(!std::__unsigned_integer<const unsigned long long>);
+static_assert(!std::__unsigned_integer<const unsigned char>);
+static_assert(!std::__unsigned_integer<const char>);
+static_assert(!std::__unsigned_integer<const bool>);
+static_assert(!std::__unsigned_integer<const char8_t>);
+static_assert(!std::__unsigned_integer<const char16_t>);
+static_assert(!std::__unsigned_integer<const char32_t>);
+static_assert(!std::__unsigned_integer<unsigned int&>);
+static_assert(!std::__unsigned_integer<const unsigned int&>);
+
+// Extended unsigned integer types per [basic.fundamental]/p3 Note 1.
+#if TEST_HAS_EXTENSION(bit_int)
+static_assert(std::__unsigned_integer<unsigned _BitInt(8)>);
+static_assert(std::__unsigned_integer<unsigned _BitInt(16)>);
+static_assert(std::__unsigned_integer<unsigned _BitInt(64)>);
+static_assert(std::__unsigned_integer<unsigned _BitInt(13)>);
+static_assert(!std::__unsigned_integer<signed _BitInt(16)>);
+static_assert(!std::__unsigned_integer<const unsigned _BitInt(16)>);
+static_assert(!std::__unsigned_integer<volatile unsigned _BitInt(64)>);
+static_assert(!std::__unsigned_integer<const volatile unsigned _BitInt(13)>);
+# if __BITINT_MAXWIDTH__ >= 128
+static_assert(std::__unsigned_integer<unsigned _BitInt(128)>);
+static_assert(!std::__unsigned_integer<const unsigned _BitInt(128)>);
+# endif
+#endif
diff --git a/libcxx/test/std/containers/views/mdspan/extents/index_type.verify.cpp b/libcxx/test/std/containers/views/mdspan/extents/index_type.verify.cpp
index ba6941a1ab4c1..5d1b62d9bbae6 100644
--- a/libcxx/test/std/containers/views/mdspan/extents/index_type.verify.cpp
+++ b/libcxx/test/std/containers/views/mdspan/extents/index_type.verify.cpp
@@ -35,6 +35,14 @@ void invalid_index_types() {
// expected-error@*:* {{static assertion failed: extents::index_type must be a signed or unsigned integer type}}
[[maybe_unused]] std::extents<wchar_t, L'*'> ewc;
#endif
+ // cv-qualified IndexType is not a signed/unsigned integer type per
+ // [basic.fundamental]/p1-2 and [basic.type.qualifier].
+ // expected-error@*:* {{static assertion failed: extents::index_type must be a signed or unsigned integer type}}
+ [[maybe_unused]] std::extents<const int, 1> eci;
+ // expected-error@*:* {{static assertion failed: extents::index_type must be a signed or unsigned integer type}}
+ [[maybe_unused]] std::extents<volatile int, 1> evi;
+ // expected-error@*:* {{static assertion failed: extents::index_type must be a signed or unsigned integer type}}
+ [[maybe_unused]] std::extents<const volatile unsigned, 1> ecvu;
}
void invalid_extent_values() {
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_cast.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_cast.compile.pass.cpp
index b8d015811798b..bf09237404b56 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_cast.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_cast.compile.pass.cpp
@@ -77,3 +77,24 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
+
+// saturating_cast<R>(t) takes the target type R as an explicit template
+// argument (cast-like). cv-qualified R is not a signed/unsigned integer
+// type per [basic.fundamental]/p1-2 and is rejected by the constraint.
+template <class R>
+concept can_cast_R = requires(int x) { std::saturating_cast<R>(x); };
+
+// Unqualified signed/unsigned integers pass; bool stays rejected.
+static_assert(can_cast_R<int>);
+static_assert(can_cast_R<unsigned int>);
+static_assert(can_cast_R<long long>);
+static_assert(!can_cast_R<bool>);
+static_assert(!can_cast_R<char>);
+
+// cv-qualified versions are rejected.
+static_assert(!can_cast_R<const int>);
+static_assert(!can_cast_R<volatile int>);
+static_assert(!can_cast_R<const volatile int>);
+static_assert(!can_cast_R<const unsigned int>);
+static_assert(!can_cast_R<const bool>);
+static_assert(!can_cast_R<const char>);
diff --git a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
index 07ea511ae30a7..4458f6afb2fc9 100644
--- a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
+++ b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
@@ -143,5 +143,15 @@ int main(int, char**) {
test_uchars<char16_t>();
test_uchars<char32_t>();
+ // in_range<R>(t) takes the target type R as an explicit template argument
+ // (cast-like). cv-qualified R is not a signed/unsigned integer type per
+ // [basic.fundamental]/p1-2 and is rejected by the constraint.
+ std::in_range<const int>(0); // expected-error {{no matching function for call to 'in_range'}}
+ std::in_range<volatile int>(0); // expected-error {{no matching function for call to 'in_range'}}
+ std::in_range<const volatile int>(0); // expected-error {{no matching function for call to 'in_range'}}
+ std::in_range<const unsigned long>(0); // expected-error {{no matching function for call to 'in_range'}}
+ std::in_range<const bool>(0); // expected-error {{no matching function for call to 'in_range'}}
+ std::in_range<const char>(0); // expected-error {{no matching function for call to 'in_range'}}
+
return 0;
}
More information about the libcxx-commits
mailing list