[libcxx-commits] [libcxx] [libc++] Gate _BitInt library extensions behind opt-in macro per P3666R4 (PR #203841)
Xavier Roche via libcxx-commits
libcxx-commits at lists.llvm.org
Mon Jun 15 01:16:33 PDT 2026
https://github.com/xroche created https://github.com/llvm/llvm-project/pull/203841
P3666R4 "Bit-precise integers" (LEWG-approved at Brno 2026-06) says libc++ should NOT support `_BitInt(N)` in `<bit>`, `<charconv>`, `<format>`, `std::hash`, `std::cmp_*`, or `<numeric>`'s saturation arithmetic. Type traits, `numeric_limits` and `<cmath>` conversion paths stay supported.
libc++ already ships experimental `_BitInt` coverage in those gated areas (PR #196512 for `byteswap`, partial coverage from #185027 etc.). This PR adds `_LIBCPP_ENABLE_BITINT_EXTENSIONS` as the user-facing opt-in macro and `_LIBCPP_HAS_BITINT_EXTENSIONS` as the internal 0/1 flag. The default is OFF so the default build matches P3666R4's stance and provides the implementation experience LEWG asked for, including negative tests demonstrating the rejection.
## How
The gating uses a single helper trait `__admits_bitint_extension_v` in a new `<__type_traits/is_bit_precise_integer.h>`. `integer_traits.h`'s `__is_signed_integer_v` and `__is_unsigned_integer_v` incorporate the trait, which cascades the gate through every consumer of the `__signed_integer` / `__unsigned_integer` concepts (`bit_log2`, `cmp_*`, saturation, `format_arg_store`, `mdspan`). `byteswap`, `hash`, `to_chars`, `from_chars` use `is_integral<T>::value` or `std::integral` directly, so each picks up the gate via an explicit constraint addition.
Existing positive `_BitInt` tests get `ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS` so they still exercise the experimental path. Two new libcxx-internal tests cover the contract:
- `bitint_extensions_disabled.verify.cpp`: under default, every gated facility rejects `_BitInt` across signedness, widths 1/8/13/32/64/128 and cv-qualified forms, while type traits and standard integers remain accepted.
- `bitint_extensions_enabled.compile.pass.cpp`: under the macro, SFINAE-friendly detection idioms report the same facilities as callable for `_BitInt` operands.
Discussion: https://discourse.llvm.org/t/implementing-p3666r4-bit-precise-integers-in-libc/91070
Assisted-by: Claude (Anthropic)
>From 50cba5036b3b1727a3e2c67cb9654c5a22769496 Mon Sep 17 00:00:00 2001
From: Xavier Roche <xavier.roche at algolia.com>
Date: Mon, 15 Jun 2026 10:15:51 +0200
Subject: [PATCH] [libc++] Gate _BitInt library extensions behind opt-in macro
per P3666R4
P3666R4 "Bit-precise integers" (LEWG-approved at Brno 2026-06) takes
the position that libc++ should NOT support _BitInt(N) in <bit>,
<charconv>, <format>, std::hash, std::cmp_*, or <numeric>'s saturation
arithmetic. Type traits, numeric_limits and <cmath> conversion paths
stay supported.
libc++ already ships experimental _BitInt support in those gated
areas (PR #196512 for byteswap, #185027 partially, etc.). This commit
adds _LIBCPP_ENABLE_BITINT_EXTENSIONS as the user-facing opt-in macro
and _LIBCPP_HAS_BITINT_EXTENSIONS as the internal 0/1 flag. The
default is OFF so the default build matches the paper's stance and
provides the implementation experience LEWG asked for (including
negative tests demonstrating the rejection).
The gating uses a single helper trait __admits_bitint_extension_v in
a new <__type_traits/is_bit_precise_integer.h>. integer_traits.h's
__is_signed_integer_v and __is_unsigned_integer_v incorporate the
trait, which cascades the gate through every consumer of the
__signed_integer / __unsigned_integer concepts (bit_log2, cmp_*,
saturation, format_arg_store, mdspan). byteswap, hash, to_chars,
from_chars use is_integral<T>::value or std::integral directly, so
each picks up the gate via an explicit constraint addition.
Existing positive _BitInt tests get ADDITIONAL_COMPILE_FLAGS:
-D_LIBCPP_ENABLE_BITINT_EXTENSIONS so they still exercise the
experimental path. Two new libcxx-internal tests cover the contract:
- bitint_extensions_disabled.verify.cpp: under default, every
gated facility rejects _BitInt across signedness, widths
1/8/13/32/64/128 and cv-qualified forms, while type traits and
standard integers remain accepted.
- bitint_extensions_enabled.compile.pass.cpp: under the macro,
SFINAE-friendly detection idioms report the same facilities as
callable for _BitInt operands.
Discussion: https://discourse.llvm.org/t/implementing-p3666r4-bit-precise-integers-in-libc/91070
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__bit/byteswap.h | 9 +-
.../include/__charconv/from_chars_integral.h | 5 +-
libcxx/include/__charconv/to_chars_integral.h | 5 +-
libcxx/include/__configuration/experimental.h | 11 ++
libcxx/include/__functional/hash.h | 10 +-
libcxx/include/__type_traits/integer_traits.h | 9 +-
.../__type_traits/is_bit_precise_integer.h | 46 ++++++
libcxx/include/module.modulemap.in | 1 +
.../bitint_extensions_disabled.verify.cpp | 149 ++++++++++++++++++
...bitint_extensions_enabled.compile.pass.cpp | 64 ++++++++
.../bit/bit.pow.two/bit_ceil.pass.cpp | 2 +
.../bit/bit.pow.two/bit_floor.pass.cpp | 2 +
.../bit/bit.pow.two/bit_width.pass.cpp | 2 +
.../bit/bit.pow.two/has_single_bit.pass.cpp | 2 +
.../bit/bitops.count/countl_one.pass.cpp | 2 +
.../bit/bitops.count/countl_zero.pass.cpp | 2 +
.../bit/bitops.count/countr_one.pass.cpp | 2 +
.../bit/bitops.count/countr_zero.pass.cpp | 2 +
.../bit/bitops.count/popcount.pass.cpp | 2 +
.../std/numerics/bit/bitops.rot/rotl.pass.cpp | 2 +
.../std/numerics/bit/bitops.rot/rotr.pass.cpp | 2 +
.../test/std/numerics/bit/byteswap.pass.cpp | 2 +
.../test/std/numerics/bit/byteswap.verify.cpp | 2 +
.../saturating.bitint.pass.cpp | 2 +
.../make_format_args.bitint.verify.cpp | 2 +
.../utility.intcmp/intcmp.bitint.pass.cpp | 2 +
27 files changed, 330 insertions(+), 12 deletions(-)
create mode 100644 libcxx/include/__type_traits/is_bit_precise_integer.h
create mode 100644 libcxx/test/libcxx/bitint_extensions_disabled.verify.cpp
create mode 100644 libcxx/test/libcxx/bitint_extensions_enabled.compile.pass.cpp
diff --git a/libcxx/include/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index cb7ae820bc012..f22d83eba3949 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -844,6 +844,7 @@ set(files
__type_traits/is_array.h
__type_traits/is_assignable.h
__type_traits/is_base_of.h
+ __type_traits/is_bit_precise_integer.h
__type_traits/is_callable.h
__type_traits/is_char_like_type.h
__type_traits/is_class.h
diff --git a/libcxx/include/__bit/byteswap.h b/libcxx/include/__bit/byteswap.h
index 43490d080910b..b3ed7853d09c7 100644
--- a/libcxx/include/__bit/byteswap.h
+++ b/libcxx/include/__bit/byteswap.h
@@ -12,6 +12,7 @@
#include <__concepts/arithmetic.h>
#include <__config>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_same.h>
#include <__type_traits/remove_cv.h>
#include <climits>
@@ -27,12 +28,16 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 23
template <integral _Tp>
+ requires __admits_bitint_extension_v<_Tp>
[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp byteswap(_Tp __val) noexcept {
// [bit.byteswap]/Mandates: T does not have padding bits.
// bool is grandfathered: every shipping implementation admits it and the
// size-1 identity path can't shuffle padding bits into value positions.
- // LWG 4583 proposes relaxing this to allow byte-aligned padding (e.g.
- // _BitInt(48) where 2 whole bytes are padding); revisit once it resolves.
+ // _BitInt operands are gated by _LIBCPP_HAS_BITINT_EXTENSIONS via the
+ // __admits_bitint_extension constraint above; default rejects them per
+ // P3666R4 §8.2.9. LWG 4583 (NAD 2026-06-09) raised relaxing the padding
+ // Mandate so byte-aligned padding (_BitInt(48) etc.) could participate;
+ // that path is gated behind a future paper.
static_assert(is_same_v<remove_cv_t<_Tp>, bool> ||
numeric_limits<_Tp>::digits + numeric_limits<_Tp>::is_signed == sizeof(_Tp) * CHAR_BIT,
"std::byteswap requires T to have no padding bits");
diff --git a/libcxx/include/__charconv/from_chars_integral.h b/libcxx/include/__charconv/from_chars_integral.h
index 3063c0978c8fa..a7b44118a2af1 100644
--- a/libcxx/include/__charconv/from_chars_integral.h
+++ b/libcxx/include/__charconv/from_chars_integral.h
@@ -16,6 +16,7 @@
#include <__config>
#include <__system_error/errc.h>
#include <__type_traits/enable_if.h>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_integral.h>
#include <__type_traits/is_signed.h>
#include <__type_traits/is_unsigned.h>
@@ -216,13 +217,13 @@ __from_chars_integral(const char* __first, const char* __last, _Tp& __value, int
return std::__sign_combinator(__first, __last, __value, __from_chars_integral<__t>, __base);
}
-template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
+template <typename _Tp, __enable_if_t<is_integral<_Tp>::value && __admits_bitint_extension_v<_Tp>, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
from_chars(const char* __first, const char* __last, _Tp& __value) {
return std::__from_chars_atoi(__first, __last, __value);
}
-template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
+template <typename _Tp, __enable_if_t<is_integral<_Tp>::value && __admits_bitint_extension_v<_Tp>, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI from_chars_result
from_chars(const char* __first, const char* __last, _Tp& __value, int __base) {
_LIBCPP_ASSERT_UNCATEGORIZED(2 <= __base && __base <= 36, "base not in [2, 36]");
diff --git a/libcxx/include/__charconv/to_chars_integral.h b/libcxx/include/__charconv/to_chars_integral.h
index 6d425139260b6..48f7f5c08fc36 100644
--- a/libcxx/include/__charconv/to_chars_integral.h
+++ b/libcxx/include/__charconv/to_chars_integral.h
@@ -22,6 +22,7 @@
#include <__system_error/errc.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/integral_constant.h>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_integral.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_signed.h>
@@ -318,7 +319,7 @@ _LIBCPP_HIDE_FROM_ABI inline _LIBCPP_CONSTEXPR_SINCE_CXX14 char __hex_to_upper(c
to_chars_result to_chars(char*, char*, bool, int = 10) = delete;
-template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
+template <typename _Tp, __enable_if_t<is_integral<_Tp>::value && __admits_bitint_extension_v<_Tp>, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
to_chars(char* __first, char* __last, _Tp __value) {
using _Type = __make_32_64_or_128_bit_t<_Tp>;
@@ -326,7 +327,7 @@ to_chars(char* __first, char* __last, _Tp __value) {
return std::__to_chars_itoa(__first, __last, static_cast<_Type>(__value), is_signed<_Tp>());
}
-template <typename _Tp, __enable_if_t<is_integral<_Tp>::value, int> = 0>
+template <typename _Tp, __enable_if_t<is_integral<_Tp>::value && __admits_bitint_extension_v<_Tp>, int> = 0>
inline _LIBCPP_CONSTEXPR_SINCE_CXX23 _LIBCPP_HIDE_FROM_ABI to_chars_result
to_chars(char* __first, char* __last, _Tp __value, int __base) {
_LIBCPP_ASSERT_UNCATEGORIZED(2 <= __base && __base <= 36, "base not in [2, 36]");
diff --git a/libcxx/include/__configuration/experimental.h b/libcxx/include/__configuration/experimental.h
index bb38d8297c63d..2d058ce97c276 100644
--- a/libcxx/include/__configuration/experimental.h
+++ b/libcxx/include/__configuration/experimental.h
@@ -35,4 +35,15 @@
#define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
#define _LIBCPP_HAS_EXPERIMENTAL_OPTIONAL_ITERATOR _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
+// _BitInt(N) library extensions beyond P3666R4 (LEWG-approved at Brno 2026-06):
+// std::byteswap, std::hash, std::to_chars, std::from_chars, std::cmp_*, etc.
+// accepting _BitInt operands. Disabled by default so the default build matches
+// P3666's "prevent library support for _BitInt" stance; opt in via
+// _LIBCPP_ENABLE_BITINT_EXTENSIONS to use the experimental coverage.
+#if defined(_LIBCPP_ENABLE_BITINT_EXTENSIONS) || defined(_LIBCPP_BUILDING_LIBRARY)
+# define _LIBCPP_HAS_BITINT_EXTENSIONS 1
+#else
+# define _LIBCPP_HAS_BITINT_EXTENSIONS 0
+#endif
+
#endif // _LIBCPP___CONFIGURATION_EXPERIMENTAL_H
diff --git a/libcxx/include/__functional/hash.h b/libcxx/include/__functional/hash.h
index c794f57356ae7..c16b2f9ca5997 100644
--- a/libcxx/include/__functional/hash.h
+++ b/libcxx/include/__functional/hash.h
@@ -17,6 +17,7 @@
#include <__type_traits/conjunction.h>
#include <__type_traits/enable_if.h>
#include <__type_traits/invoke.h>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_constructible.h>
#include <__type_traits/is_enum.h>
#include <__type_traits/is_floating_point.h>
@@ -369,16 +370,17 @@ struct __hash_impl<_Tp, __enable_if_t<is_enum<_Tp>::value && __is_unqualified_v<
};
template <class _Tp>
-struct __hash_impl<
- _Tp,
- __enable_if_t<is_integral<_Tp>::value && __is_unqualified_v<_Tp> && (sizeof(_Tp) <= sizeof(size_t))> >
+struct __hash_impl< _Tp,
+ __enable_if_t<is_integral<_Tp>::value && __is_unqualified_v<_Tp> &&
+ __admits_bitint_extension_v<_Tp> && (sizeof(_Tp) <= sizeof(size_t))> >
: __unary_function<_Tp, size_t> {
_LIBCPP_HIDE_FROM_ABI size_t operator()(_Tp __v) const _NOEXCEPT { return static_cast<size_t>(__v); }
};
template <class _Tp>
struct __hash_impl<_Tp,
- __enable_if_t<is_integral<_Tp>::value && __is_unqualified_v<_Tp> && (sizeof(_Tp) > sizeof(size_t))> >
+ __enable_if_t<is_integral<_Tp>::value && __is_unqualified_v<_Tp> &&
+ __admits_bitint_extension_v<_Tp> && (sizeof(_Tp) > sizeof(size_t))> >
: __scalar_hash<_Tp> {};
template <class _Tp>
diff --git a/libcxx/include/__type_traits/integer_traits.h b/libcxx/include/__type_traits/integer_traits.h
index a0fbcd5c3ecd7..a3d6bf85f58a8 100644
--- a/libcxx/include/__type_traits/integer_traits.h
+++ b/libcxx/include/__type_traits/integer_traits.h
@@ -10,6 +10,7 @@
#define _LIBCPP___TYPE_TRAITS_INTEGER_TRAITS_H
#include <__config>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_integral.h>
#include <__type_traits/is_same.h>
#include <__type_traits/is_signed.h>
@@ -42,15 +43,19 @@ inline const bool __is_character_v<char16_t> = true;
template <>
inline const bool __is_character_v<char32_t> = true;
+// _BitInt(N) participates only when _LIBCPP_HAS_BITINT_EXTENSIONS is on;
+// otherwise the concepts derived from these traits (e.g. __signed_integer,
+// __unsigned_integer) match P3666R4's "prevent library support for _BitInt"
+// stance and reject _BitInt operands.
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_unqualified_v<_Tp>;
+ __is_unqualified_v<_Tp> && __admits_bitint_extension_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_unqualified_v<_Tp>;
+ __is_unqualified_v<_Tp> && __admits_bitint_extension_v<_Tp>;
#if _LIBCPP_STD_VER >= 20
template <class _Tp>
diff --git a/libcxx/include/__type_traits/is_bit_precise_integer.h b/libcxx/include/__type_traits/is_bit_precise_integer.h
new file mode 100644
index 0000000000000..6203cf6b2c4d4
--- /dev/null
+++ b/libcxx/include/__type_traits/is_bit_precise_integer.h
@@ -0,0 +1,46 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+#ifndef _LIBCPP___TYPE_TRAITS_IS_BIT_PRECISE_INTEGER_H
+#define _LIBCPP___TYPE_TRAITS_IS_BIT_PRECISE_INTEGER_H
+
+#include <__config>
+#include <__type_traits/remove_cv.h>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+// True iff T (cv-qualifiers stripped) is a bit-precise integer type, i.e.
+// _BitInt(N) or unsigned _BitInt(N). Used to gate libc++'s experimental
+// _BitInt library extensions per the _LIBCPP_HAS_BITINT_EXTENSIONS knob.
+
+template <class _Tp>
+inline const bool __is_bit_precise_integer_impl_v = false;
+
+#if __has_extension(bit_int)
+template <int _Nb>
+inline const bool __is_bit_precise_integer_impl_v<_BitInt(_Nb)> = true;
+template <int _Nb>
+inline const bool __is_bit_precise_integer_impl_v<unsigned _BitInt(_Nb)> = true;
+#endif
+
+template <class _Tp>
+inline const bool __is_bit_precise_integer_v = __is_bit_precise_integer_impl_v<__remove_cv_t<_Tp> >;
+
+// True iff T may participate in libc++'s _BitInt-aware overloads. Standard
+// (non bit-precise) types always pass. _BitInt types pass only when the
+// extension macro is on.
+template <class _Tp>
+inline const bool __admits_bitint_extension_v = _LIBCPP_HAS_BITINT_EXTENSIONS || !__is_bit_precise_integer_v<_Tp>;
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___TYPE_TRAITS_IS_BIT_PRECISE_INTEGER_H
diff --git a/libcxx/include/module.modulemap.in b/libcxx/include/module.modulemap.in
index b11055940a82c..8dea95ded81fd 100644
--- a/libcxx/include/module.modulemap.in
+++ b/libcxx/include/module.modulemap.in
@@ -129,6 +129,7 @@ module std_core [system] {
header "__type_traits/is_base_of.h"
export std_core.type_traits.integral_constant
}
+ module is_bit_precise_integer { header "__type_traits/is_bit_precise_integer.h" }
module is_callable {
header "__type_traits/is_callable.h"
export std_core.type_traits.integral_constant
diff --git a/libcxx/test/libcxx/bitint_extensions_disabled.verify.cpp b/libcxx/test/libcxx/bitint_extensions_disabled.verify.cpp
new file mode 100644
index 0000000000000..f2e7bd500c535
--- /dev/null
+++ b/libcxx/test/libcxx/bitint_extensions_disabled.verify.cpp
@@ -0,0 +1,149 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Verifies that with _LIBCPP_ENABLE_BITINT_EXTENSIONS undefined (the default),
+// libc++ rejects _BitInt(N) operands in the standard library facilities that
+// P3666R4 §5 says should not accept _BitInt: <bit>, <charconv>, <format>,
+// std::hash, std::cmp_*, <numeric>'s saturation arithmetic. Type traits and
+// numeric_limits stay unaffected because P3666 keeps those.
+
+#include <bit>
+#include <charconv>
+#include <format>
+#include <functional>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+#if !TEST_HAS_EXTENSION(bit_int)
+// Without _BitInt, this test has nothing to verify.
+// expected-no-diagnostics
+#else
+
+using S = _BitInt(64);
+using U = unsigned _BitInt(64);
+using SN = _BitInt(13); // non-byte-aligned, exercises the padding path under macro=1
+using UN = unsigned _BitInt(13);
+
+// std::byteswap — P3666R4 §8.2.9 excludes bit-precise integers.
+void test_byteswap(U __u, S __s, const U __cu, volatile U __vu) {
+ (void)std::byteswap(__u); // expected-error@*:* {{no matching function for call to 'byteswap'}}
+ (void)std::byteswap(__s); // expected-error@*:* {{no matching function for call to 'byteswap'}}
+ (void)std::byteswap(__cu); // expected-error@*:* {{no matching function for call to 'byteswap'}}
+ (void)std::byteswap(__vu); // expected-error@*:* {{no matching function for call to 'byteswap'}}
+}
+
+// std::hash — P3666R4 keeps hash silent on _BitInt; the partial specialization
+// in <__functional/hash.h> is gated by __admits_bitint_extension_v.
+void test_hash(U __u, S __s) {
+ (void)std::hash<U>{}(__u); // expected-error@*:* {{call to deleted constructor}}
+ (void)std::hash<S>{}(__s); // expected-error@*:* {{call to deleted constructor}}
+}
+
+// std::to_chars / std::from_chars — P3666R4 §5 excludes _BitInt.
+void test_charconv(char* __b, char* __e, U __u, S __s) {
+ (void)std::to_chars(__b, __e, __u); // expected-error@*:* {{no matching function for call to 'to_chars'}}
+ (void)std::to_chars(__b, __e, __s); // expected-error@*:* {{no matching function for call to 'to_chars'}}
+ (void)std::from_chars(__b, __e, __u); // expected-error@*:* {{no matching function for call to 'from_chars'}}
+ (void)std::from_chars(__b, __e, __s); // expected-error@*:* {{no matching function for call to 'from_chars'}}
+}
+
+// std::cmp_* — uses __signed_integer / __unsigned_integer concepts that
+// inherit the bitint gate from integer_traits.h.
+void test_cmp(U __u, S __s) {
+ (void)std::cmp_less(__u, 0); // expected-error@*:* {{no matching function for call to 'cmp_less'}}
+ (void)std::cmp_less(__s, 0); // expected-error@*:* {{no matching function for call to 'cmp_less'}}
+ (void)std::cmp_equal(0, __u); // expected-error@*:* {{no matching function for call to 'cmp_equal'}}
+ (void)std::cmp_greater(0, __s); // expected-error@*:* {{no matching function for call to 'cmp_greater'}}
+}
+
+// Saturation arithmetic — same concept path.
+void test_saturation(U __u, S __s) {
+ (void)std::add_sat(__u, __u); // expected-error@*:* {{no matching function for call to 'add_sat'}}
+ (void)std::sub_sat(__s, __s); // expected-error@*:* {{no matching function for call to 'sub_sat'}}
+ (void)std::saturate_cast<U>(0); // expected-error@*:* {{no matching function for call to 'saturate_cast'}}
+}
+
+// std::format — format_arg_store dispatch refuses _BitInt under the gate.
+// The existing diagnostic is a static_assert from format_arg_store.
+void test_format(U __u, S __s) {
+ (void)std::make_format_args(__u); // expected-error@*:* {{static assertion failed}}
+ (void)std::make_format_args(__s); // expected-error@*:* {{static assertion failed}}
+}
+
+// Things that DO stay supported per P3666 §5 — these must compile cleanly.
+static_assert(std::is_integral_v<U>);
+static_assert(std::is_integral_v<S>);
+static_assert(std::is_signed_v<S>);
+static_assert(std::is_unsigned_v<U>);
+static_assert(std::is_same_v<std::make_unsigned_t<S>, U>);
+static_assert(std::numeric_limits<U>::digits == 64);
+
+// SFINAE-friendly: detection idioms must report "not callable" rather than
+// hard-error when probing the gated facilities. Probe a spread of widths
+// (1, 8, byte-aligned mid, sub-byte-padded, large) and both signednesses.
+template <class T>
+concept has_byteswap = requires(T t) { std::byteswap(t); };
+
+static_assert(!has_byteswap<_BitInt(1)>);
+static_assert(!has_byteswap<unsigned _BitInt(1)>);
+static_assert(!has_byteswap<_BitInt(8)>);
+static_assert(!has_byteswap<unsigned _BitInt(8)>);
+static_assert(!has_byteswap<_BitInt(13)>);
+static_assert(!has_byteswap<unsigned _BitInt(13)>);
+static_assert(!has_byteswap<_BitInt(32)>);
+static_assert(!has_byteswap<_BitInt(64)>);
+static_assert(!has_byteswap<unsigned _BitInt(64)>);
+# if __BITINT_MAXWIDTH__ >= 128
+static_assert(!has_byteswap<_BitInt(128)>);
+static_assert(!has_byteswap<unsigned _BitInt(128)>);
+# endif
+
+// cv-qualified _BitInt also rejected (matches P3666's "possibly cv-qualified"
+// exclusion wording).
+static_assert(!has_byteswap<const _BitInt(64)>);
+static_assert(!has_byteswap<volatile unsigned _BitInt(64)>);
+static_assert(!has_byteswap<const volatile _BitInt(64)>);
+
+// Standard integers still work via the same detection path.
+static_assert(has_byteswap<int>);
+static_assert(has_byteswap<unsigned long long>);
+static_assert(has_byteswap<short>);
+static_assert(has_byteswap<bool>);
+
+// Same SFINAE check across the other gated facilities — confirms the gate
+// is wired correctly per facility, not only for byteswap.
+template <class T>
+concept has_hash = requires(T t) { std::hash<T>{}(t); };
+template <class T>
+concept has_to_chars = requires(char* b, char* e, T t) { std::to_chars(b, e, t); };
+template <class T>
+concept has_cmp_less = requires(T t) { std::cmp_less(t, 0); };
+template <class T>
+concept has_add_sat = requires(T t) { std::add_sat(t, t); };
+
+static_assert(!has_hash<U>);
+static_assert(!has_hash<S>);
+static_assert(!has_to_chars<U>);
+static_assert(!has_to_chars<S>);
+static_assert(!has_cmp_less<U>);
+static_assert(!has_cmp_less<S>);
+static_assert(!has_add_sat<U>);
+static_assert(!has_add_sat<S>);
+
+// And standard integers still flow through.
+static_assert(has_hash<int>);
+static_assert(has_to_chars<int>);
+static_assert(has_cmp_less<int>);
+static_assert(has_add_sat<unsigned>);
+
+#endif // TEST_HAS_EXTENSION(bit_int)
diff --git a/libcxx/test/libcxx/bitint_extensions_enabled.compile.pass.cpp b/libcxx/test/libcxx/bitint_extensions_enabled.compile.pass.cpp
new file mode 100644
index 0000000000000..f895f2d577e2e
--- /dev/null
+++ b/libcxx/test/libcxx/bitint_extensions_enabled.compile.pass.cpp
@@ -0,0 +1,64 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
+// Companion to bitint_extensions_disabled.verify.cpp. With the opt-in macro
+// on, libc++'s _BitInt extensions are visible: SFINAE-friendly detection
+// reports the facilities as callable for byte-aligned _BitInt operands.
+// Sub-byte-padded widths (e.g. _BitInt(13)) still fail their function-level
+// padding-bit Mandate where applicable (byteswap), but pass SFINAE.
+
+#include <bit>
+#include <charconv>
+#include <format>
+#include <functional>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+#if TEST_HAS_EXTENSION(bit_int)
+
+using S64 = _BitInt(64);
+using U64 = unsigned _BitInt(64);
+using U8 = unsigned _BitInt(8);
+
+// Concept-style detection — under the macro, the gated facilities accept
+// _BitInt operands. (byteswap with byte-aligned widths still passes its
+// own padding-bit Mandate; widths with padding fail it, not the gate.)
+template <class T>
+concept has_byteswap = requires(T t) { std::byteswap(t); };
+template <class T>
+concept has_hash = requires(T t) { std::hash<T>{}(t); };
+template <class T>
+concept has_to_chars = requires(char* b, char* e, T t) { std::to_chars(b, e, t); };
+template <class T>
+concept has_cmp_less = requires(T t) { std::cmp_less(t, 0); };
+template <class T>
+concept has_add_sat = requires(T t) { std::add_sat(t, t); };
+
+static_assert(has_byteswap<U64>);
+static_assert(has_byteswap<S64>);
+static_assert(has_byteswap<U8>);
+static_assert(has_hash<U64>);
+static_assert(has_hash<S64>);
+static_assert(has_to_chars<U64>);
+static_assert(has_to_chars<S64>);
+static_assert(has_cmp_less<U64>);
+static_assert(has_cmp_less<S64>);
+static_assert(has_add_sat<U64>);
+static_assert(has_add_sat<S64>);
+
+// Type-trait surface is identical regardless of the macro — P3666 keeps it.
+static_assert(std::is_integral_v<U64>);
+static_assert(std::is_integral_v<S64>);
+
+#endif // TEST_HAS_EXTENSION(bit_int)
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.pass.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.pass.cpp
index 1aaddafe40cc7..6f596cc8642c6 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/bit_ceil.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr T bit_ceil(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/bit_floor.pass.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/bit_floor.pass.cpp
index 07dae010b99fa..bb780fa2932c2 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/bit_floor.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/bit_floor.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr T bit_floor(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/bit_width.pass.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/bit_width.pass.cpp
index efba0dcd2b77b..4d6f5db781941 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/bit_width.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/bit_width.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int bit_width(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bit.pow.two/has_single_bit.pass.cpp b/libcxx/test/std/numerics/bit/bit.pow.two/has_single_bit.pass.cpp
index 6bab2b9f9069a..f384a50ec10fb 100644
--- a/libcxx/test/std/numerics/bit/bit.pow.two/has_single_bit.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bit.pow.two/has_single_bit.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr bool has_single_bit(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.count/countl_one.pass.cpp b/libcxx/test/std/numerics/bit/bitops.count/countl_one.pass.cpp
index 39d5db1ed22a8..6c7241021971b 100644
--- a/libcxx/test/std/numerics/bit/bitops.count/countl_one.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.count/countl_one.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int countl_one(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.count/countl_zero.pass.cpp b/libcxx/test/std/numerics/bit/bitops.count/countl_zero.pass.cpp
index a73175d51a201..80094c758e528 100644
--- a/libcxx/test/std/numerics/bit/bitops.count/countl_zero.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.count/countl_zero.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int countl_zero(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.count/countr_one.pass.cpp b/libcxx/test/std/numerics/bit/bitops.count/countr_one.pass.cpp
index ba350a76d96af..f1b5f8253c78a 100644
--- a/libcxx/test/std/numerics/bit/bitops.count/countr_one.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.count/countr_one.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int countr_one(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.count/countr_zero.pass.cpp b/libcxx/test/std/numerics/bit/bitops.count/countr_zero.pass.cpp
index e7e9d6542ab86..c2ceb917f2742 100644
--- a/libcxx/test/std/numerics/bit/bitops.count/countr_zero.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.count/countr_zero.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int countr_zero(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.count/popcount.pass.cpp b/libcxx/test/std/numerics/bit/bitops.count/popcount.pass.cpp
index dc5cdf89f147b..6226108fc3c22 100644
--- a/libcxx/test/std/numerics/bit/bitops.count/popcount.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.count/popcount.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int popcount(T x) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.rot/rotl.pass.cpp b/libcxx/test/std/numerics/bit/bitops.rot/rotl.pass.cpp
index e9859ac6398b3..ebb776128a903 100644
--- a/libcxx/test/std/numerics/bit/bitops.rot/rotl.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.rot/rotl.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int rotl(T x, unsigned int s) noexcept;
diff --git a/libcxx/test/std/numerics/bit/bitops.rot/rotr.pass.cpp b/libcxx/test/std/numerics/bit/bitops.rot/rotr.pass.cpp
index 428e11dba4969..6920559ae7efb 100644
--- a/libcxx/test/std/numerics/bit/bitops.rot/rotr.pass.cpp
+++ b/libcxx/test/std/numerics/bit/bitops.rot/rotr.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// template <class T>
// constexpr int rotr(T x, unsigned int s) noexcept;
diff --git a/libcxx/test/std/numerics/bit/byteswap.pass.cpp b/libcxx/test/std/numerics/bit/byteswap.pass.cpp
index f96af9410ead3..024bf6fcf6616 100644
--- a/libcxx/test/std/numerics/bit/byteswap.pass.cpp
+++ b/libcxx/test/std/numerics/bit/byteswap.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
#include <bit>
#include <cassert>
#include <cstddef>
diff --git a/libcxx/test/std/numerics/bit/byteswap.verify.cpp b/libcxx/test/std/numerics/bit/byteswap.verify.cpp
index f7ff1c6aefb11..49f2df04eabc1 100644
--- a/libcxx/test/std/numerics/bit/byteswap.verify.cpp
+++ b/libcxx/test/std/numerics/bit/byteswap.verify.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17, c++20
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// <bit>
// std::byteswap rejects integer types that have padding bits per
diff --git a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating.bitint.pass.cpp b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating.bitint.pass.cpp
index a4c68b0d582ad..dc0f3e6436228 100644
--- a/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating.bitint.pass.cpp
+++ b/libcxx/test/std/numerics/numeric.ops/numeric.ops.sat/saturating.bitint.pass.cpp
@@ -8,6 +8,8 @@
// REQUIRES: std-at-least-c++26
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// <numeric>
// add_sat, sub_sat, mul_sat, div_sat, saturate_cast applied to _BitInt(N).
diff --git a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp
index 52107b8b91527..a8bff3119a04e 100644
--- a/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp
+++ b/libcxx/test/std/utilities/format/format.arguments/format.arg.store/make_format_args.bitint.verify.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// <format>
// make_format_args with _BitInt(N) wider than __int128 is unsupported.
diff --git a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.bitint.pass.cpp b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.bitint.pass.cpp
index f96ac1c9f7a32..b6bc251642036 100644
--- a/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.bitint.pass.cpp
+++ b/libcxx/test/std/utilities/utility/utility.intcmp/intcmp.bitint.pass.cpp
@@ -8,6 +8,8 @@
// UNSUPPORTED: c++03, c++11, c++14, c++17
+// ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS
+
// <utility>
// cmp_equal, cmp_not_equal, cmp_less, cmp_less_equal, cmp_greater,
More information about the libcxx-commits
mailing list