[libcxx-commits] [libcxx] [libc++] Reject cv-qualified types in __is_signed/unsigned_integer_v (PR #200377)
Xavier Roche via libcxx-commits
libcxx-commits at lists.llvm.org
Fri May 29 07:47:03 PDT 2026
https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/200377
>From a5ae4e1131e3139de2e039b29784c0c8cdb721f8 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 13:42:52 +0200
Subject: [PATCH 1/6] [libc++] Reject cv-qualified types in
__is_signed/unsigned_integer_v
Address jwakely's review feedback on PR #185027: the standard's
definition of /signed integer type/ and /unsigned integer type/ in
[basic.fundamental]/p1-2 is a specific list of unqualified types
(plus extended integer types). cv-qualified versions are distinct
types per [basic.type.qualifier] and so are NOT signed/unsigned
integer types.
The previous implementation wrongly admitted `const int`, `volatile
int`, `const volatile int`, `const char`, `const bool`, and similar
cv-qualified versions. The fix adds `is_same<__remove_cv_t<T>, T>`
to both traits.
Tests cover the three meaningful cv flavors (const, volatile, const
volatile), references, character types, bool, and _BitInt(N)
extended integer types.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__type_traits/integer_traits.h | 16 ++++++----
.../__libcpp_signed_integer.compile.pass.cpp | 32 +++++++++++++++++++
...__libcpp_unsigned_integer.compile.pass.cpp | 32 +++++++++++++++++++
3 files changed, 73 insertions(+), 7 deletions(-)
diff --git a/libcxx/include/__type_traits/integer_traits.h b/libcxx/include/__type_traits/integer_traits.h
index d7ac89be9c2a7..e0a9e5bcc98eb 100644
--- a/libcxx/include/__type_traits/integer_traits.h
+++ b/libcxx/include/__type_traits/integer_traits.h
@@ -14,6 +14,7 @@
#include <__type_traits/is_same.h>
#include <__type_traits/is_signed.h>
#include <__type_traits/is_unsigned.h>
+#include <__type_traits/remove_cv.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC 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_same<__remove_cv_t<_Tp>, _Tp>::value;
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_same<__remove_cv_t<_Tp>, _Tp>::value;
#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
>From 50b9185ffe793ceaba9c5ede84aa3aa4182bd21b Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 14:45:39 +0200
Subject: [PATCH 2/6] [libc++] Use __is_unqualified_v and add user-facing cv
regression tests
Address Nikolas/jwakely review feedback on PR #200377:
1. Swap `is_same<__remove_cv_t<_Tp>, _Tp>::value` to `__is_unqualified_v<_Tp>`,
the established libc++ idiom (uses compiler intrinsics
`__is_same`/`__remove_cvref`, cheaper than library-trait instantiation).
2. Add user-facing regression tests covering frederick-vs-ja's enumerated
templates that consume the trait:
Mandates (verify.cpp with expected-error):
- `<utility>`: cmp_equal, cmp_not_equal, cmp_less, cmp_less_equal,
cmp_greater, cmp_greater_equal, in_range (covers jwakely's specific
`std::in_range<const int>(0)` libstdc++-parity bug).
- `<mdspan>`: `extents<const int, ...>` and cv variants.
Constraints (compile.pass.cpp with concept-based detection):
- `<bit>` (new bit.cv_qualified.compile.pass.cpp): has_single_bit,
bit_ceil, bit_floor, bit_width, rotl, rotr, countl_zero, countl_one,
countr_zero, countr_one, popcount.
- `<numeric>`: saturating_add, saturating_sub, saturating_mul,
saturating_div, saturating_cast (both R and T arms).
`byteswap` is NOT in this list: its Constraints clause is `T models
integral`, which admits cv-qualified types.
Note: `<format>` is also indirectly affected (arg-store dispatch consumes
`__signed_integer`/`__unsigned_integer`), but `__create_format_arg` strips
`const` via `remove_const_t` so non-volatile cv paths remain working.
`format("{}", volatile_int)` is a deliberate spec-correctness behavior
change matching libstdc++.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/__type_traits/integer_traits.h | 6 +-
.../mdspan/extents/index_type.verify.cpp | 8 ++
.../bit/bit.cv_qualified.compile.pass.cpp | 95 +++++++++++++++++++
.../bit/bit.pow.two/bit_ceil.verify.cpp | 7 ++
.../saturating_add.compile.pass.cpp | 13 +++
.../saturating_cast.compile.pass.cpp | 17 ++++
.../saturating_div.compile.pass.cpp | 13 +++
.../saturating_mul.compile.pass.cpp | 13 +++
.../saturating_sub.compile.pass.cpp | 13 +++
.../utility/utility.intcmp/intcmp.verify.cpp | 20 ++++
10 files changed, 202 insertions(+), 3 deletions(-)
create mode 100644 libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
diff --git a/libcxx/include/__type_traits/integer_traits.h b/libcxx/include/__type_traits/integer_traits.h
index e0a9e5bcc98eb..a0fbcd5c3ecd7 100644
--- a/libcxx/include/__type_traits/integer_traits.h
+++ b/libcxx/include/__type_traits/integer_traits.h
@@ -13,8 +13,8 @@
#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>
-#include <__type_traits/remove_cv.h>
#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
# pragma GCC system_header
@@ -45,12 +45,12 @@ 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_same<__remove_cv_t<_Tp>, _Tp>::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_same<__remove_cv_t<_Tp>, _Tp>::value;
+ __is_unqualified_v<_Tp>;
#if _LIBCPP_STD_VER >= 20
template <class _Tp>
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..36922c0dac2ba 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 types are not signed/unsigned integer types per
+ // [basic.fundamental]/p1-2.
+ // 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/bit/bit.cv_qualified.compile.pass.cpp b/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
new file mode 100644
index 0000000000000..7353a3a764f6d
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
@@ -0,0 +1,95 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <bit>
+
+// Regression test for [bit] templates constrained on __unsigned_integer:
+// cv-qualified types are not unsigned integer types per [basic.fundamental]
+// /p2 and must be rejected by the SFINAE constraint. Explicit template args
+// bypass the by-value deduction strip, so the rejection has to live in the
+// constraint, not just at call site.
+//
+// std::byteswap is NOT in this list: its Constraints clause is `T models
+// integral`, which admits cv-qualified types.
+
+#include <bit>
+
+template <class T>
+concept _has_bit_ceil = requires { std::bit_ceil<T>(T{}); };
+template <class T>
+concept _has_bit_floor = requires { std::bit_floor<T>(T{}); };
+template <class T>
+concept _has_bit_width = requires { std::bit_width<T>(T{}); };
+template <class T>
+concept _has_has_single_bit = requires { std::has_single_bit<T>(T{}); };
+template <class T>
+concept _has_rotl = requires { std::rotl<T>(T{}, 0); };
+template <class T>
+concept _has_rotr = requires { std::rotr<T>(T{}, 0); };
+template <class T>
+concept _has_countl_zero = requires { std::countl_zero<T>(T{}); };
+template <class T>
+concept _has_countl_one = requires { std::countl_one<T>(T{}); };
+template <class T>
+concept _has_countr_zero = requires { std::countr_zero<T>(T{}); };
+template <class T>
+concept _has_countr_one = requires { std::countr_one<T>(T{}); };
+template <class T>
+concept _has_popcount = requires { std::popcount<T>(T{}); };
+
+template <class T>
+constexpr void check_admitted() {
+ static_assert(_has_bit_ceil<T>);
+ static_assert(_has_bit_floor<T>);
+ static_assert(_has_bit_width<T>);
+ static_assert(_has_has_single_bit<T>);
+ static_assert(_has_rotl<T>);
+ static_assert(_has_rotr<T>);
+ static_assert(_has_countl_zero<T>);
+ static_assert(_has_countl_one<T>);
+ static_assert(_has_countr_zero<T>);
+ static_assert(_has_countr_one<T>);
+ static_assert(_has_popcount<T>);
+}
+
+template <class T>
+constexpr void check_rejected() {
+ static_assert(!_has_bit_ceil<T>);
+ static_assert(!_has_bit_floor<T>);
+ static_assert(!_has_bit_width<T>);
+ static_assert(!_has_has_single_bit<T>);
+ static_assert(!_has_rotl<T>);
+ static_assert(!_has_rotr<T>);
+ static_assert(!_has_countl_zero<T>);
+ static_assert(!_has_countl_one<T>);
+ static_assert(!_has_countr_zero<T>);
+ static_assert(!_has_countr_one<T>);
+ static_assert(!_has_popcount<T>);
+}
+
+// Unqualified unsigned integer types pass.
+template void check_admitted<unsigned int>();
+template void check_admitted<unsigned long>();
+template void check_admitted<unsigned long long>();
+
+// cv-qualified versions of unsigned integer types are rejected.
+template void check_rejected<const unsigned int>();
+template void check_rejected<volatile unsigned int>();
+template void check_rejected<const volatile unsigned int>();
+template void check_rejected<const unsigned long>();
+template void check_rejected<const unsigned long long>();
+
+// Signed and character types stay rejected.
+template void check_rejected<int>();
+template void check_rejected<const int>();
+template void check_rejected<bool>();
+template void check_rejected<const bool>();
+template void check_rejected<char>();
+template void check_rejected<const char>();
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
index d37de690a48db..10abd5ba4a00e 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
@@ -47,5 +47,12 @@ int main(int, char**)
static_assert(toobig<std::uintmax_t>(), ""); // expected-error {{static assertion expression is not an integral constant expression}}
static_assert(toobig<std::uintptr_t>(), ""); // expected-error {{static assertion expression is not an integral constant expression}}
+ // cv-qualified versions are not unsigned integer types per
+ // [basic.fundamental]/p2. Explicit template args bypass by-value
+ // deduction strip, so the constraint must reject these.
+ std::bit_ceil<const unsigned int>(0u); // expected-error {{no matching function for call to 'bit_ceil'}}
+ std::bit_ceil<volatile unsigned int>(0u); // expected-error {{no matching function for call to 'bit_ceil'}}
+ std::bit_ceil<const volatile unsigned long>(0ul); // expected-error {{no matching function for call to 'bit_ceil'}}
+
return 0;
}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
index 03c7dc724acfb..988c37d074a3b 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
@@ -75,3 +75,16 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
+
+// cv-qualified versions are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. Explicit template args bypass by-value
+// deduction strip, so the constraint must reject these.
+template <class T>
+concept _can_saturating_add = requires(int x) { std::saturating_add<T>(x, x); };
+static_assert(!_can_saturating_add<const int>);
+static_assert(!_can_saturating_add<volatile int>);
+static_assert(!_can_saturating_add<const volatile int>);
+static_assert(!_can_saturating_add<const unsigned int>);
+static_assert(!_can_saturating_add<const long long>);
+static_assert(!_can_saturating_add<const bool>);
+static_assert(!_can_saturating_add<const char>);
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..f7ccfe10bc702 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,20 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
+
+// cv-qualified versions are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. saturating_cast<R>(T) constrains BOTH R and T
+// on __signed_or_unsigned_integer; either being cv-qualified must reject.
+template <class R>
+concept _can_cast_R = requires(int x) { std::saturating_cast<R>(x); };
+template <class T>
+concept _can_cast_T = requires(T x) { std::saturating_cast<int, T>(x); };
+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>);
+static_assert(!_can_cast_T<const int>);
+static_assert(!_can_cast_T<volatile int>);
+static_assert(!_can_cast_T<const volatile int>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
index f644b11db05cd..ebefabd41f4cf 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
@@ -106,3 +106,16 @@ static_assert(!CanDivByZero<0ULL>);
#ifndef TEST_HAS_NO_INT128
static_assert(!CanDivByZero<static_cast<__uint128_t>(0)>);
#endif
+
+// cv-qualified versions are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. Explicit template args bypass by-value
+// deduction strip, so the constraint must reject these.
+template <class T>
+concept _can_saturating_div = requires(int x) { std::saturating_div<T>(x, x); };
+static_assert(!_can_saturating_div<const int>);
+static_assert(!_can_saturating_div<volatile int>);
+static_assert(!_can_saturating_div<const volatile int>);
+static_assert(!_can_saturating_div<const unsigned int>);
+static_assert(!_can_saturating_div<const long long>);
+static_assert(!_can_saturating_div<const bool>);
+static_assert(!_can_saturating_div<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
index 418c479cd22c6..aa214006a8402 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
@@ -75,3 +75,16 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
+
+// cv-qualified versions are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. Explicit template args bypass by-value
+// deduction strip, so the constraint must reject these.
+template <class T>
+concept _can_saturating_mul = requires(int x) { std::saturating_mul<T>(x, x); };
+static_assert(!_can_saturating_mul<const int>);
+static_assert(!_can_saturating_mul<volatile int>);
+static_assert(!_can_saturating_mul<const volatile int>);
+static_assert(!_can_saturating_mul<const unsigned int>);
+static_assert(!_can_saturating_mul<const long long>);
+static_assert(!_can_saturating_mul<const bool>);
+static_assert(!_can_saturating_mul<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
index 9676d29b2c32c..6bc6d7ebaa96c 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
@@ -75,3 +75,16 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
+
+// cv-qualified versions are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. Explicit template args bypass by-value
+// deduction strip, so the constraint must reject these.
+template <class T>
+concept _can_saturating_sub = requires(int x) { std::saturating_sub<T>(x, x); };
+static_assert(!_can_saturating_sub<const int>);
+static_assert(!_can_saturating_sub<volatile int>);
+static_assert(!_can_saturating_sub<const volatile int>);
+static_assert(!_can_saturating_sub<const unsigned int>);
+static_assert(!_can_saturating_sub<const long long>);
+static_assert(!_can_saturating_sub<const bool>);
+static_assert(!_can_saturating_sub<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..babbbde6c8ab4 100644
--- a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
+++ b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
@@ -121,6 +121,26 @@ constexpr void test_uchars() {
std::in_range<int>(T()); // expected-error 2 {{no matching function for call to 'in_range'}}
}
+// cv-qualified types are not signed/unsigned integer types per
+// [basic.fundamental]/p1-2. Explicit template args bypass the by-value
+// deduction strip, so the constraint must reject these.
+void test_cv() {
+ std::cmp_equal<const int, const int>(0, 0); // expected-error {{no matching function for call to 'cmp_equal'}}
+ std::cmp_not_equal<const int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_not_equal'}}
+ std::cmp_less<const int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
+ std::cmp_less<int, volatile int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
+ std::cmp_less_equal<volatile int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less_equal'}}
+ std::cmp_greater<const volatile int, const int>(
+ 0, 0); // expected-error {{no matching function for call to 'cmp_greater'}}
+ std::cmp_greater_equal<const int, const int>(
+ 0, 0); // expected-error {{no matching function for call to 'cmp_greater_equal'}}
+ 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 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'}}
+ std::in_range<const unsigned long>(0); // expected-error {{no matching function for call to 'in_range'}}
+}
+
int main(int, char**) {
test<bool>();
test<char>();
>From ba3c0fc5193e1d54b34309d5a82920f46de76510 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 14:54:58 +0200
Subject: [PATCH 3/6] [libc++] Add cv-rejection regression test for
make_format_args
`__create_format_arg` strips `const` via `remove_const_t` before
dispatch, so `make_format_args(const_int_lvalue)` still works
(classified as a signed integer). It does NOT strip `volatile`, so
with the cv-rejection now in `__signed_integer`/`__unsigned_integer`,
a `volatile int` lvalue hits `__arg_t::__none` and trips the existing
"the supplied type is not formattable" static_assert.
Lock in both behaviors so a future refactor does not silently change
them.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../make_format_args.cv.verify.cpp | 53 +++++++++++++++++++
.../utility/utility.intcmp/intcmp.verify.cpp | 8 +--
2 files changed, 57 insertions(+), 4 deletions(-)
create mode 100644 libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
new file mode 100644
index 0000000000000..7d12932349583
--- /dev/null
+++ b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
@@ -0,0 +1,53 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14, c++17
+
+// <format>
+
+// make_format_args of a cv-qualified integer.
+//
+// `__create_format_arg` applies `remove_const_t` before dispatch via
+// `__determine_arg_t`, so a `const int` lvalue is still classified as a
+// signed integer. `volatile` is not stripped: with the no-cv guard in
+// `__signed_integer`/`__unsigned_integer`, `volatile int` no longer maps
+// to a storage slot and trips the "not formattable" static_assert chain.
+// Pin down both behaviors so a future refactor does not silently change
+// them.
+
+#include <format>
+
+#include "test_macros.h"
+
+// Three volatile lvalues below each trigger:
+// - format_arg_store.h: "the supplied type is not formattable"
+// - format_arg_store.h: a follow-on static_assert
+// - constructible.h: implicitly-deleted copy constructor cascade
+// expected-error@*:* 3 {{the supplied type is not formattable}}
+// expected-error@*:* 3+ {{static assertion failed}}
+// expected-error@*:* 3+ {{implicitly-deleted copy constructor}}
+
+void f_const_int_ok() {
+ const int value = 0;
+ (void)std::make_format_args(value); // ok: const stripped before classification
+}
+
+void f_volatile_int() {
+ volatile int value = 0;
+ (void)std::make_format_args(value);
+}
+
+void f_const_volatile_int() {
+ const volatile int value = 0;
+ (void)std::make_format_args(value);
+}
+
+void f_volatile_unsigned() {
+ volatile unsigned value = 0;
+ (void)std::make_format_args(value);
+}
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 babbbde6c8ab4..93d452f5e3706 100644
--- a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
+++ b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
@@ -130,10 +130,10 @@ void test_cv() {
std::cmp_less<const int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
std::cmp_less<int, volatile int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
std::cmp_less_equal<volatile int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less_equal'}}
- std::cmp_greater<const volatile int, const int>(
- 0, 0); // expected-error {{no matching function for call to 'cmp_greater'}}
- std::cmp_greater_equal<const int, const int>(
- 0, 0); // expected-error {{no matching function for call to 'cmp_greater_equal'}}
+ // expected-error at +1 {{no matching function for call to 'cmp_greater'}}
+ std::cmp_greater<const volatile int, const int>(0, 0);
+ // expected-error at +1 {{no matching function for call to 'cmp_greater_equal'}}
+ std::cmp_greater_equal<const int, const int>(0, 0);
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 bool>(0); // expected-error {{no matching function for call to 'in_range'}}
>From 5e9355b8339dd62986d96b00129b4489eca4bc67 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 15:51:22 +0200
Subject: [PATCH 4/6] [libc++] Address @frederick-vs-ja review on PR #200377
Three points:
1. Switch new test files to `// REQUIRES: std-at-least-c++20` instead
of `// UNSUPPORTED: c++03, c++11, c++14, c++17`. Applies to
`bit.cv_qualified.compile.pass.cpp` and `make_format_args.cv.verify.cpp`.
2. Drop leading underscores from test-file identifiers (reserved in
the global namespace). `_has_bit_ceil` -> `has_bit_ceil` etc. in
`bit.cv_qualified.compile.pass.cpp`, and `_can_saturating_*` ->
`can_saturating_*` in the saturation `.compile.pass.cpp` files.
3. Add positive-admission assertions for every cv-rejection concept,
so a buggy always-false concept does not silently pass the test.
Each saturation test now has:
static_assert(can_saturating_X<int>);
static_assert(can_saturating_X<unsigned int>);
static_assert(!can_saturating_X<bool>);
`bit.cv_qualified.compile.pass.cpp` already had this via
`check_admitted<unsigned int>()`.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../bit/bit.cv_qualified.compile.pass.cpp | 68 +++++++++----------
.../saturating_add.compile.pass.cpp | 25 ++++---
.../saturating_cast.compile.pass.cpp | 32 ++++++---
.../saturating_div.compile.pass.cpp | 25 ++++---
.../saturating_mul.compile.pass.cpp | 25 ++++---
.../saturating_sub.compile.pass.cpp | 25 ++++---
.../make_format_args.cv.verify.cpp | 2 +-
7 files changed, 124 insertions(+), 78 deletions(-)
diff --git a/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp b/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
index 7353a3a764f6d..bdb88168996d9 100644
--- a/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
// <bit>
@@ -22,56 +22,56 @@
#include <bit>
template <class T>
-concept _has_bit_ceil = requires { std::bit_ceil<T>(T{}); };
+concept has_bit_ceil = requires { std::bit_ceil<T>(T{}); };
template <class T>
-concept _has_bit_floor = requires { std::bit_floor<T>(T{}); };
+concept has_bit_floor = requires { std::bit_floor<T>(T{}); };
template <class T>
-concept _has_bit_width = requires { std::bit_width<T>(T{}); };
+concept has_bit_width = requires { std::bit_width<T>(T{}); };
template <class T>
-concept _has_has_single_bit = requires { std::has_single_bit<T>(T{}); };
+concept has_has_single_bit = requires { std::has_single_bit<T>(T{}); };
template <class T>
-concept _has_rotl = requires { std::rotl<T>(T{}, 0); };
+concept has_rotl = requires { std::rotl<T>(T{}, 0); };
template <class T>
-concept _has_rotr = requires { std::rotr<T>(T{}, 0); };
+concept has_rotr = requires { std::rotr<T>(T{}, 0); };
template <class T>
-concept _has_countl_zero = requires { std::countl_zero<T>(T{}); };
+concept has_countl_zero = requires { std::countl_zero<T>(T{}); };
template <class T>
-concept _has_countl_one = requires { std::countl_one<T>(T{}); };
+concept has_countl_one = requires { std::countl_one<T>(T{}); };
template <class T>
-concept _has_countr_zero = requires { std::countr_zero<T>(T{}); };
+concept has_countr_zero = requires { std::countr_zero<T>(T{}); };
template <class T>
-concept _has_countr_one = requires { std::countr_one<T>(T{}); };
+concept has_countr_one = requires { std::countr_one<T>(T{}); };
template <class T>
-concept _has_popcount = requires { std::popcount<T>(T{}); };
+concept has_popcount = requires { std::popcount<T>(T{}); };
template <class T>
constexpr void check_admitted() {
- static_assert(_has_bit_ceil<T>);
- static_assert(_has_bit_floor<T>);
- static_assert(_has_bit_width<T>);
- static_assert(_has_has_single_bit<T>);
- static_assert(_has_rotl<T>);
- static_assert(_has_rotr<T>);
- static_assert(_has_countl_zero<T>);
- static_assert(_has_countl_one<T>);
- static_assert(_has_countr_zero<T>);
- static_assert(_has_countr_one<T>);
- static_assert(_has_popcount<T>);
+ static_assert(has_bit_ceil<T>);
+ static_assert(has_bit_floor<T>);
+ static_assert(has_bit_width<T>);
+ static_assert(has_has_single_bit<T>);
+ static_assert(has_rotl<T>);
+ static_assert(has_rotr<T>);
+ static_assert(has_countl_zero<T>);
+ static_assert(has_countl_one<T>);
+ static_assert(has_countr_zero<T>);
+ static_assert(has_countr_one<T>);
+ static_assert(has_popcount<T>);
}
template <class T>
constexpr void check_rejected() {
- static_assert(!_has_bit_ceil<T>);
- static_assert(!_has_bit_floor<T>);
- static_assert(!_has_bit_width<T>);
- static_assert(!_has_has_single_bit<T>);
- static_assert(!_has_rotl<T>);
- static_assert(!_has_rotr<T>);
- static_assert(!_has_countl_zero<T>);
- static_assert(!_has_countl_one<T>);
- static_assert(!_has_countr_zero<T>);
- static_assert(!_has_countr_one<T>);
- static_assert(!_has_popcount<T>);
+ static_assert(!has_bit_ceil<T>);
+ static_assert(!has_bit_floor<T>);
+ static_assert(!has_bit_width<T>);
+ static_assert(!has_has_single_bit<T>);
+ static_assert(!has_rotl<T>);
+ static_assert(!has_rotr<T>);
+ static_assert(!has_countl_zero<T>);
+ static_assert(!has_countl_one<T>);
+ static_assert(!has_countr_zero<T>);
+ static_assert(!has_countr_one<T>);
+ static_assert(!has_popcount<T>);
}
// Unqualified unsigned integer types pass.
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
index 988c37d074a3b..107bf8cc4728a 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
@@ -80,11 +80,20 @@ constexpr void test() {
// [basic.fundamental]/p1-2. Explicit template args bypass by-value
// deduction strip, so the constraint must reject these.
template <class T>
-concept _can_saturating_add = requires(int x) { std::saturating_add<T>(x, x); };
-static_assert(!_can_saturating_add<const int>);
-static_assert(!_can_saturating_add<volatile int>);
-static_assert(!_can_saturating_add<const volatile int>);
-static_assert(!_can_saturating_add<const unsigned int>);
-static_assert(!_can_saturating_add<const long long>);
-static_assert(!_can_saturating_add<const bool>);
-static_assert(!_can_saturating_add<const char>);
+concept can_saturating_add = requires(int x) { std::saturating_add<T>(x, x); };
+
+// Unqualified signed/unsigned integers pass; bool stays rejected.
+static_assert(can_saturating_add<int>);
+static_assert(can_saturating_add<unsigned int>);
+static_assert(can_saturating_add<long long>);
+static_assert(!can_saturating_add<bool>);
+static_assert(!can_saturating_add<char>);
+
+// cv-qualified versions are rejected.
+static_assert(!can_saturating_add<const int>);
+static_assert(!can_saturating_add<volatile int>);
+static_assert(!can_saturating_add<const volatile int>);
+static_assert(!can_saturating_add<const unsigned int>);
+static_assert(!can_saturating_add<const long long>);
+static_assert(!can_saturating_add<const bool>);
+static_assert(!can_saturating_add<const char>);
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 f7ccfe10bc702..3db3d841fdfaa 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
@@ -82,15 +82,25 @@ constexpr void test() {
// [basic.fundamental]/p1-2. saturating_cast<R>(T) constrains BOTH R and T
// on __signed_or_unsigned_integer; either being cv-qualified must reject.
template <class R>
-concept _can_cast_R = requires(int x) { std::saturating_cast<R>(x); };
+concept can_cast_R = requires(int x) { std::saturating_cast<R>(x); };
template <class T>
-concept _can_cast_T = requires(T x) { std::saturating_cast<int, T>(x); };
-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>);
-static_assert(!_can_cast_T<const int>);
-static_assert(!_can_cast_T<volatile int>);
-static_assert(!_can_cast_T<const volatile int>);
+concept can_cast_T = requires(T x) { std::saturating_cast<int, T>(x); };
+
+// Unqualified signed/unsigned integers pass on both R and T sides.
+static_assert(can_cast_R<int>);
+static_assert(can_cast_R<unsigned int>);
+static_assert(can_cast_T<long long>);
+static_assert(!can_cast_R<bool>);
+static_assert(!can_cast_R<char>);
+static_assert(!can_cast_T<bool>);
+
+// cv-qualified versions are rejected on either arm.
+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>);
+static_assert(!can_cast_T<const int>);
+static_assert(!can_cast_T<volatile int>);
+static_assert(!can_cast_T<const volatile int>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
index ebefabd41f4cf..8c6b8dc08c65c 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
@@ -111,11 +111,20 @@ static_assert(!CanDivByZero<static_cast<__uint128_t>(0)>);
// [basic.fundamental]/p1-2. Explicit template args bypass by-value
// deduction strip, so the constraint must reject these.
template <class T>
-concept _can_saturating_div = requires(int x) { std::saturating_div<T>(x, x); };
-static_assert(!_can_saturating_div<const int>);
-static_assert(!_can_saturating_div<volatile int>);
-static_assert(!_can_saturating_div<const volatile int>);
-static_assert(!_can_saturating_div<const unsigned int>);
-static_assert(!_can_saturating_div<const long long>);
-static_assert(!_can_saturating_div<const bool>);
-static_assert(!_can_saturating_div<const char>);
+concept can_saturating_div = requires(int x) { std::saturating_div<T>(x, x); };
+
+// Unqualified signed/unsigned integers pass; bool stays rejected.
+static_assert(can_saturating_div<int>);
+static_assert(can_saturating_div<unsigned int>);
+static_assert(can_saturating_div<long long>);
+static_assert(!can_saturating_div<bool>);
+static_assert(!can_saturating_div<char>);
+
+// cv-qualified versions are rejected.
+static_assert(!can_saturating_div<const int>);
+static_assert(!can_saturating_div<volatile int>);
+static_assert(!can_saturating_div<const volatile int>);
+static_assert(!can_saturating_div<const unsigned int>);
+static_assert(!can_saturating_div<const long long>);
+static_assert(!can_saturating_div<const bool>);
+static_assert(!can_saturating_div<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
index aa214006a8402..b4a5cbea8f48e 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
@@ -80,11 +80,20 @@ constexpr void test() {
// [basic.fundamental]/p1-2. Explicit template args bypass by-value
// deduction strip, so the constraint must reject these.
template <class T>
-concept _can_saturating_mul = requires(int x) { std::saturating_mul<T>(x, x); };
-static_assert(!_can_saturating_mul<const int>);
-static_assert(!_can_saturating_mul<volatile int>);
-static_assert(!_can_saturating_mul<const volatile int>);
-static_assert(!_can_saturating_mul<const unsigned int>);
-static_assert(!_can_saturating_mul<const long long>);
-static_assert(!_can_saturating_mul<const bool>);
-static_assert(!_can_saturating_mul<const char>);
+concept can_saturating_mul = requires(int x) { std::saturating_mul<T>(x, x); };
+
+// Unqualified signed/unsigned integers pass; bool stays rejected.
+static_assert(can_saturating_mul<int>);
+static_assert(can_saturating_mul<unsigned int>);
+static_assert(can_saturating_mul<long long>);
+static_assert(!can_saturating_mul<bool>);
+static_assert(!can_saturating_mul<char>);
+
+// cv-qualified versions are rejected.
+static_assert(!can_saturating_mul<const int>);
+static_assert(!can_saturating_mul<volatile int>);
+static_assert(!can_saturating_mul<const volatile int>);
+static_assert(!can_saturating_mul<const unsigned int>);
+static_assert(!can_saturating_mul<const long long>);
+static_assert(!can_saturating_mul<const bool>);
+static_assert(!can_saturating_mul<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
index 6bc6d7ebaa96c..7a6e7497aff47 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
@@ -80,11 +80,20 @@ constexpr void test() {
// [basic.fundamental]/p1-2. Explicit template args bypass by-value
// deduction strip, so the constraint must reject these.
template <class T>
-concept _can_saturating_sub = requires(int x) { std::saturating_sub<T>(x, x); };
-static_assert(!_can_saturating_sub<const int>);
-static_assert(!_can_saturating_sub<volatile int>);
-static_assert(!_can_saturating_sub<const volatile int>);
-static_assert(!_can_saturating_sub<const unsigned int>);
-static_assert(!_can_saturating_sub<const long long>);
-static_assert(!_can_saturating_sub<const bool>);
-static_assert(!_can_saturating_sub<const char>);
+concept can_saturating_sub = requires(int x) { std::saturating_sub<T>(x, x); };
+
+// Unqualified signed/unsigned integers pass; bool stays rejected.
+static_assert(can_saturating_sub<int>);
+static_assert(can_saturating_sub<unsigned int>);
+static_assert(can_saturating_sub<long long>);
+static_assert(!can_saturating_sub<bool>);
+static_assert(!can_saturating_sub<char>);
+
+// cv-qualified versions are rejected.
+static_assert(!can_saturating_sub<const int>);
+static_assert(!can_saturating_sub<volatile int>);
+static_assert(!can_saturating_sub<const volatile int>);
+static_assert(!can_saturating_sub<const unsigned int>);
+static_assert(!can_saturating_sub<const long long>);
+static_assert(!can_saturating_sub<const bool>);
+static_assert(!can_saturating_sub<const char>);
diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
index 7d12932349583..9d4716be16c45 100644
--- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
+++ b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
@@ -6,7 +6,7 @@
//
//===----------------------------------------------------------------------===//
-// UNSUPPORTED: c++03, c++11, c++14, c++17
+// REQUIRES: std-at-least-c++20
// <format>
>From ffce06c407ecd4c6ce7fe21523d6e9dda9e776a4 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 16:33:13 +0200
Subject: [PATCH 5/6] [libc++] Drop user-facing cv-rejection regression tests
per @philnik777
@philnik777 noted the explicit-template-arg cv-rejection tests document
pathological usage that real users never write: by-value parameter
deduction strips top-level cv at the call site, so the cv path is only
reachable via `std::bit_ceil<const T>(...)` or similar syntax, which
nobody types.
Revert the per-template tests added earlier in this PR (in `<utility>`,
`<bit>`, `<numeric>`, `<mdspan>`, `<format>`). The trait-level tests in
`__libcpp_{signed,unsigned}_integer.compile.pass.cpp` already cover
every consumer of the concept by construction: any future regression in
cv-handling would surface there, since all 24 templates share the same
`__signed_or_unsigned_integer` machinery.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../mdspan/extents/index_type.verify.cpp | 8 --
.../bit/bit.cv_qualified.compile.pass.cpp | 95 -------------------
.../bit/bit.pow.two/bit_ceil.verify.cpp | 7 --
.../saturating_add.compile.pass.cpp | 22 -----
.../saturating_cast.compile.pass.cpp | 27 ------
.../saturating_div.compile.pass.cpp | 22 -----
.../saturating_mul.compile.pass.cpp | 22 -----
.../saturating_sub.compile.pass.cpp | 22 -----
.../make_format_args.cv.verify.cpp | 53 -----------
.../utility/utility.intcmp/intcmp.verify.cpp | 20 ----
10 files changed, 298 deletions(-)
delete mode 100644 libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
delete mode 100644 libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
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 36922c0dac2ba..ba6941a1ab4c1 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,14 +35,6 @@ 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 types are not signed/unsigned integer types per
- // [basic.fundamental]/p1-2.
- // 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/bit/bit.cv_qualified.compile.pass.cpp b/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
deleted file mode 100644
index bdb88168996d9..0000000000000
--- a/libcxx/test/std/numerics/bit/bit.cv_qualified.compile.pass.cpp
+++ /dev/null
@@ -1,95 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// REQUIRES: std-at-least-c++20
-
-// <bit>
-
-// Regression test for [bit] templates constrained on __unsigned_integer:
-// cv-qualified types are not unsigned integer types per [basic.fundamental]
-// /p2 and must be rejected by the SFINAE constraint. Explicit template args
-// bypass the by-value deduction strip, so the rejection has to live in the
-// constraint, not just at call site.
-//
-// std::byteswap is NOT in this list: its Constraints clause is `T models
-// integral`, which admits cv-qualified types.
-
-#include <bit>
-
-template <class T>
-concept has_bit_ceil = requires { std::bit_ceil<T>(T{}); };
-template <class T>
-concept has_bit_floor = requires { std::bit_floor<T>(T{}); };
-template <class T>
-concept has_bit_width = requires { std::bit_width<T>(T{}); };
-template <class T>
-concept has_has_single_bit = requires { std::has_single_bit<T>(T{}); };
-template <class T>
-concept has_rotl = requires { std::rotl<T>(T{}, 0); };
-template <class T>
-concept has_rotr = requires { std::rotr<T>(T{}, 0); };
-template <class T>
-concept has_countl_zero = requires { std::countl_zero<T>(T{}); };
-template <class T>
-concept has_countl_one = requires { std::countl_one<T>(T{}); };
-template <class T>
-concept has_countr_zero = requires { std::countr_zero<T>(T{}); };
-template <class T>
-concept has_countr_one = requires { std::countr_one<T>(T{}); };
-template <class T>
-concept has_popcount = requires { std::popcount<T>(T{}); };
-
-template <class T>
-constexpr void check_admitted() {
- static_assert(has_bit_ceil<T>);
- static_assert(has_bit_floor<T>);
- static_assert(has_bit_width<T>);
- static_assert(has_has_single_bit<T>);
- static_assert(has_rotl<T>);
- static_assert(has_rotr<T>);
- static_assert(has_countl_zero<T>);
- static_assert(has_countl_one<T>);
- static_assert(has_countr_zero<T>);
- static_assert(has_countr_one<T>);
- static_assert(has_popcount<T>);
-}
-
-template <class T>
-constexpr void check_rejected() {
- static_assert(!has_bit_ceil<T>);
- static_assert(!has_bit_floor<T>);
- static_assert(!has_bit_width<T>);
- static_assert(!has_has_single_bit<T>);
- static_assert(!has_rotl<T>);
- static_assert(!has_rotr<T>);
- static_assert(!has_countl_zero<T>);
- static_assert(!has_countl_one<T>);
- static_assert(!has_countr_zero<T>);
- static_assert(!has_countr_one<T>);
- static_assert(!has_popcount<T>);
-}
-
-// Unqualified unsigned integer types pass.
-template void check_admitted<unsigned int>();
-template void check_admitted<unsigned long>();
-template void check_admitted<unsigned long long>();
-
-// cv-qualified versions of unsigned integer types are rejected.
-template void check_rejected<const unsigned int>();
-template void check_rejected<volatile unsigned int>();
-template void check_rejected<const volatile unsigned int>();
-template void check_rejected<const unsigned long>();
-template void check_rejected<const unsigned long long>();
-
-// Signed and character types stay rejected.
-template void check_rejected<int>();
-template void check_rejected<const int>();
-template void check_rejected<bool>();
-template void check_rejected<const bool>();
-template void check_rejected<char>();
-template void check_rejected<const char>();
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
index 10abd5ba4a00e..d37de690a48db 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.verify.cpp
@@ -47,12 +47,5 @@ int main(int, char**)
static_assert(toobig<std::uintmax_t>(), ""); // expected-error {{static assertion expression is not an integral constant expression}}
static_assert(toobig<std::uintptr_t>(), ""); // expected-error {{static assertion expression is not an integral constant expression}}
- // cv-qualified versions are not unsigned integer types per
- // [basic.fundamental]/p2. Explicit template args bypass by-value
- // deduction strip, so the constraint must reject these.
- std::bit_ceil<const unsigned int>(0u); // expected-error {{no matching function for call to 'bit_ceil'}}
- std::bit_ceil<volatile unsigned int>(0u); // expected-error {{no matching function for call to 'bit_ceil'}}
- std::bit_ceil<const volatile unsigned long>(0ul); // expected-error {{no matching function for call to 'bit_ceil'}}
-
return 0;
}
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
index 107bf8cc4728a..03c7dc724acfb 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_add.compile.pass.cpp
@@ -75,25 +75,3 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
-
-// cv-qualified versions are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. Explicit template args bypass by-value
-// deduction strip, so the constraint must reject these.
-template <class T>
-concept can_saturating_add = requires(int x) { std::saturating_add<T>(x, x); };
-
-// Unqualified signed/unsigned integers pass; bool stays rejected.
-static_assert(can_saturating_add<int>);
-static_assert(can_saturating_add<unsigned int>);
-static_assert(can_saturating_add<long long>);
-static_assert(!can_saturating_add<bool>);
-static_assert(!can_saturating_add<char>);
-
-// cv-qualified versions are rejected.
-static_assert(!can_saturating_add<const int>);
-static_assert(!can_saturating_add<volatile int>);
-static_assert(!can_saturating_add<const volatile int>);
-static_assert(!can_saturating_add<const unsigned int>);
-static_assert(!can_saturating_add<const long long>);
-static_assert(!can_saturating_add<const bool>);
-static_assert(!can_saturating_add<const char>);
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 3db3d841fdfaa..b8d015811798b 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,30 +77,3 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
-
-// cv-qualified versions are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. saturating_cast<R>(T) constrains BOTH R and T
-// on __signed_or_unsigned_integer; either being cv-qualified must reject.
-template <class R>
-concept can_cast_R = requires(int x) { std::saturating_cast<R>(x); };
-template <class T>
-concept can_cast_T = requires(T x) { std::saturating_cast<int, T>(x); };
-
-// Unqualified signed/unsigned integers pass on both R and T sides.
-static_assert(can_cast_R<int>);
-static_assert(can_cast_R<unsigned int>);
-static_assert(can_cast_T<long long>);
-static_assert(!can_cast_R<bool>);
-static_assert(!can_cast_R<char>);
-static_assert(!can_cast_T<bool>);
-
-// cv-qualified versions are rejected on either arm.
-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>);
-static_assert(!can_cast_T<const int>);
-static_assert(!can_cast_T<volatile int>);
-static_assert(!can_cast_T<const volatile int>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
index 8c6b8dc08c65c..f644b11db05cd 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_div.compile.pass.cpp
@@ -106,25 +106,3 @@ static_assert(!CanDivByZero<0ULL>);
#ifndef TEST_HAS_NO_INT128
static_assert(!CanDivByZero<static_cast<__uint128_t>(0)>);
#endif
-
-// cv-qualified versions are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. Explicit template args bypass by-value
-// deduction strip, so the constraint must reject these.
-template <class T>
-concept can_saturating_div = requires(int x) { std::saturating_div<T>(x, x); };
-
-// Unqualified signed/unsigned integers pass; bool stays rejected.
-static_assert(can_saturating_div<int>);
-static_assert(can_saturating_div<unsigned int>);
-static_assert(can_saturating_div<long long>);
-static_assert(!can_saturating_div<bool>);
-static_assert(!can_saturating_div<char>);
-
-// cv-qualified versions are rejected.
-static_assert(!can_saturating_div<const int>);
-static_assert(!can_saturating_div<volatile int>);
-static_assert(!can_saturating_div<const volatile int>);
-static_assert(!can_saturating_div<const unsigned int>);
-static_assert(!can_saturating_div<const long long>);
-static_assert(!can_saturating_div<const bool>);
-static_assert(!can_saturating_div<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
index b4a5cbea8f48e..418c479cd22c6 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_mul.compile.pass.cpp
@@ -75,25 +75,3 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
-
-// cv-qualified versions are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. Explicit template args bypass by-value
-// deduction strip, so the constraint must reject these.
-template <class T>
-concept can_saturating_mul = requires(int x) { std::saturating_mul<T>(x, x); };
-
-// Unqualified signed/unsigned integers pass; bool stays rejected.
-static_assert(can_saturating_mul<int>);
-static_assert(can_saturating_mul<unsigned int>);
-static_assert(can_saturating_mul<long long>);
-static_assert(!can_saturating_mul<bool>);
-static_assert(!can_saturating_mul<char>);
-
-// cv-qualified versions are rejected.
-static_assert(!can_saturating_mul<const int>);
-static_assert(!can_saturating_mul<volatile int>);
-static_assert(!can_saturating_mul<const volatile int>);
-static_assert(!can_saturating_mul<const unsigned int>);
-static_assert(!can_saturating_mul<const long long>);
-static_assert(!can_saturating_mul<const bool>);
-static_assert(!can_saturating_mul<const char>);
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
index 7a6e7497aff47..9676d29b2c32c 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating_sub.compile.pass.cpp
@@ -75,25 +75,3 @@ constexpr void test() {
test_constraint_fail<double>();
test_constraint_fail<long double>();
}
-
-// cv-qualified versions are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. Explicit template args bypass by-value
-// deduction strip, so the constraint must reject these.
-template <class T>
-concept can_saturating_sub = requires(int x) { std::saturating_sub<T>(x, x); };
-
-// Unqualified signed/unsigned integers pass; bool stays rejected.
-static_assert(can_saturating_sub<int>);
-static_assert(can_saturating_sub<unsigned int>);
-static_assert(can_saturating_sub<long long>);
-static_assert(!can_saturating_sub<bool>);
-static_assert(!can_saturating_sub<char>);
-
-// cv-qualified versions are rejected.
-static_assert(!can_saturating_sub<const int>);
-static_assert(!can_saturating_sub<volatile int>);
-static_assert(!can_saturating_sub<const volatile int>);
-static_assert(!can_saturating_sub<const unsigned int>);
-static_assert(!can_saturating_sub<const long long>);
-static_assert(!can_saturating_sub<const bool>);
-static_assert(!can_saturating_sub<const char>);
diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
deleted file mode 100644
index 9d4716be16c45..0000000000000
--- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.cv.verify.cpp
+++ /dev/null
@@ -1,53 +0,0 @@
-//===----------------------------------------------------------------------===//
-//
-// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
-// See https://llvm.org/LICENSE.txt for license information.
-// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
-//
-//===----------------------------------------------------------------------===//
-
-// REQUIRES: std-at-least-c++20
-
-// <format>
-
-// make_format_args of a cv-qualified integer.
-//
-// `__create_format_arg` applies `remove_const_t` before dispatch via
-// `__determine_arg_t`, so a `const int` lvalue is still classified as a
-// signed integer. `volatile` is not stripped: with the no-cv guard in
-// `__signed_integer`/`__unsigned_integer`, `volatile int` no longer maps
-// to a storage slot and trips the "not formattable" static_assert chain.
-// Pin down both behaviors so a future refactor does not silently change
-// them.
-
-#include <format>
-
-#include "test_macros.h"
-
-// Three volatile lvalues below each trigger:
-// - format_arg_store.h: "the supplied type is not formattable"
-// - format_arg_store.h: a follow-on static_assert
-// - constructible.h: implicitly-deleted copy constructor cascade
-// expected-error@*:* 3 {{the supplied type is not formattable}}
-// expected-error@*:* 3+ {{static assertion failed}}
-// expected-error@*:* 3+ {{implicitly-deleted copy constructor}}
-
-void f_const_int_ok() {
- const int value = 0;
- (void)std::make_format_args(value); // ok: const stripped before classification
-}
-
-void f_volatile_int() {
- volatile int value = 0;
- (void)std::make_format_args(value);
-}
-
-void f_const_volatile_int() {
- const volatile int value = 0;
- (void)std::make_format_args(value);
-}
-
-void f_volatile_unsigned() {
- volatile unsigned value = 0;
- (void)std::make_format_args(value);
-}
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 93d452f5e3706..07ea511ae30a7 100644
--- a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
+++ b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.verify.cpp
@@ -121,26 +121,6 @@ constexpr void test_uchars() {
std::in_range<int>(T()); // expected-error 2 {{no matching function for call to 'in_range'}}
}
-// cv-qualified types are not signed/unsigned integer types per
-// [basic.fundamental]/p1-2. Explicit template args bypass the by-value
-// deduction strip, so the constraint must reject these.
-void test_cv() {
- std::cmp_equal<const int, const int>(0, 0); // expected-error {{no matching function for call to 'cmp_equal'}}
- std::cmp_not_equal<const int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_not_equal'}}
- std::cmp_less<const int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
- std::cmp_less<int, volatile int>(0, 0); // expected-error {{no matching function for call to 'cmp_less'}}
- std::cmp_less_equal<volatile int, int>(0, 0); // expected-error {{no matching function for call to 'cmp_less_equal'}}
- // expected-error at +1 {{no matching function for call to 'cmp_greater'}}
- std::cmp_greater<const volatile int, const int>(0, 0);
- // expected-error at +1 {{no matching function for call to 'cmp_greater_equal'}}
- std::cmp_greater_equal<const int, const int>(0, 0);
- 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 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'}}
- std::in_range<const unsigned long>(0); // expected-error {{no matching function for call to 'in_range'}}
-}
-
int main(int, char**) {
test<bool>();
test<char>();
>From 5e31c3b001fc0f08589a9a762a40d97a6131b31e Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Fri, 29 May 2026 16:46:42 +0200
Subject: [PATCH 6/6] [libc++] Restore cv-rejection tests for cast-like
templates
@philnik777 clarified: cast-like functions where the user provides an
explicit target type as a template argument do have real value to test
for cv-rejection. Three templates fit that shape:
- `std::in_range<R>(t)` -- explicit R is the target type.
- `std::saturating_cast<R>(t)` -- explicit R is the target type.
- `std::extents<IndexType, ...>` -- explicit IndexType.
Tests use the natural style for each:
- `in_range`: verify.cpp (matches existing intcmp.verify.cpp convention
for the bool/char/float rejection tests).
- `saturating_cast`: SFINAE concept `can_cast_R<T>` in compile.pass.cpp
(matches saturating_cast.compile.pass.cpp convention; includes
positive admission checks per @frederick-vs-ja).
- `extents`: verify.cpp (only option -- extents uses a Mandate-style
static_assert in the class body, not a SFINAE constraint).
Non-cast templates (cmp_*, bit_*, saturating_{add,sub,mul,div},
byteswap, make_format_args) stay without cv tests: their template args
are deduced from value arguments and explicit cv calls are not a
user-facing surface.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
.../mdspan/extents/index_type.verify.cpp | 8 +++++++
.../saturating_cast.compile.pass.cpp | 21 +++++++++++++++++++
.../utility/utility.intcmp/intcmp.verify.cpp | 10 +++++++++
3 files changed, 39 insertions(+)
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