[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:41:29 PDT 2026
https://github.com/xroche updated https://github.com/llvm/llvm-project/pull/203841
>From 8c95d631ec843cace7dcf845c577627bbf6c86c7 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_*, <numeric>'s gcd/lcm/midpoint/saturation.
Type traits, numeric_limits and <cmath> conversion paths stay supported.
libc++ already ships experimental _BitInt support in several gated areas
(PR #196512 for byteswap, partial coverage from #185027). Add
_LIBCPP_ENABLE_BITINT_EXTENSIONS as the user-facing opt-in macro and
_LIBCPP_HAS_BITINT_EXTENSIONS as the internal 0/1 flag. Off by default
to match P3666's stance. Opt in to keep the experimental coverage.
The gate flows through a helper trait __admits_bitint_extension_v in
<__type_traits/is_bit_precise_integer.h>. integer_traits.h folds the
trait into __is_signed_integer_v / __is_unsigned_integer_v, which
cascades to every consumer of __signed_integer / __unsigned_integer
(bit_log2, cmp_*, saturation, format_arg_store, mdspan). byteswap,
hash, to_chars, from_chars, gcd, lcm, midpoint use is_integral or
std::integral directly, so each picks up the gate via an explicit
constraint.
Existing positive _BitInt tests opt back in via
ADDITIONAL_COMPILE_FLAGS: -D_LIBCPP_ENABLE_BITINT_EXTENSIONS.
New libcxx-internal tests under libcxx/test/libcxx/bitint/:
- extensions_disabled.verify.cpp: under default, every gated facility
rejects _BitInt across signedness, widths 1/8/13/32/64/128 and cv
variants. Standard integers and traits stay accepted.
- extensions_enabled.compile.pass.cpp: under the macro, SFINAE-friendly
detection idioms report the same facilities as callable.
Discussion: https://discourse.llvm.org/t/implementing-p3666r4-bit-precise-integers-in-libc/91070
Follow-up surfaces not gated here: std::atomic<_BitInt(N)>'s integral
specialization is a larger change worth a separate PR; std::random's
seed_seq integer constructor is lower priority.
Assisted-by: Claude (Anthropic)
Co-Authored-By: Claude Opus 4.6 <noreply at anthropic.com>
---
libcxx/docs/UserDocumentation.rst | 8 +
libcxx/include/CMakeLists.txt | 1 +
libcxx/include/__bit/byteswap.h | 10 +-
.../include/__charconv/from_chars_integral.h | 5 +-
libcxx/include/__charconv/to_chars_integral.h | 5 +-
libcxx/include/__configuration/experimental.h | 7 +
libcxx/include/__functional/hash.h | 10 +-
libcxx/include/__numeric/gcd_lcm.h | 5 +
libcxx/include/__numeric/midpoint.h | 3 +-
libcxx/include/__type_traits/integer_traits.h | 6 +-
.../__type_traits/is_bit_precise_integer.h | 43 +++++
libcxx/include/module.modulemap.in | 1 +
.../bitint/extensions_disabled.verify.cpp | 153 ++++++++++++++++++
.../extensions_enabled.compile.pass.cpp | 51 ++++++
.../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 +
30 files changed, 324 insertions(+), 16 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/docs/UserDocumentation.rst b/libcxx/docs/UserDocumentation.rst
index 415a599168374..71a705a2df7f1 100644
--- a/libcxx/docs/UserDocumentation.rst
+++ b/libcxx/docs/UserDocumentation.rst
@@ -119,6 +119,14 @@ enable or disable extended libc++ behavior.
ensure that the appropriate experimental library (usually ``libc++experimental.a``)
is linked into their program.
+**_LIBCPP_ENABLE_BITINT_EXTENSIONS**:
+ This macro opts into libc++'s experimental ``_BitInt(N)`` support in standard
+ library facilities such as ``std::byteswap``, ``std::hash``, ``std::to_chars``,
+ ``std::from_chars``, ``std::cmp_*``, ``std::gcd``, ``std::lcm``, ``std::midpoint``,
+ and saturation arithmetic. It is off by default to match P3666R4's "prevent
+ library support for ``_BitInt``" direction. Existing user code may still rely
+ on the previously-shipping behavior by defining this macro.
+
**_LIBCPP_HARDENING_MODE**:
This macro is used to choose the :ref:`hardening mode <using-hardening-modes>`.
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..82da84c80c8c3 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,11 @@ _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.
+ // [bit.byteswap]/Mandates: T has no padding bits. bool is admitted as the
+ // size-1 identity path. _BitInt operands are gated above; see
+ // <__type_traits/is_bit_precise_integer.h>.
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..ca340d762a57d 100644
--- a/libcxx/include/__configuration/experimental.h
+++ b/libcxx/include/__configuration/experimental.h
@@ -35,4 +35,11 @@
#define _LIBCPP_HAS_EXPERIMENTAL_HARDENING_OBSERVE_SEMANTIC _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
#define _LIBCPP_HAS_EXPERIMENTAL_OPTIONAL_ITERATOR _LIBCPP_HAS_EXPERIMENTAL_LIBRARY
+// Opt-in for _BitInt(N) library extensions beyond P3666R4. Off by default.
+#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/__numeric/gcd_lcm.h b/libcxx/include/__numeric/gcd_lcm.h
index 5ab870fa7349c..f7e883dd613ed 100644
--- a/libcxx/include/__numeric/gcd_lcm.h
+++ b/libcxx/include/__numeric/gcd_lcm.h
@@ -15,6 +15,7 @@
#include <__config>
#include <__memory/addressof.h>
#include <__type_traits/common_type.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>
@@ -51,6 +52,8 @@ constexpr _LIBCPP_HIDE_FROM_ABI common_type_t<_Tp, _Up> gcd(_Tp __m, _Up __n) {
static_assert(is_integral<_Tp>::value && is_integral<_Up>::value, "Arguments to gcd must be integer types");
static_assert(!is_same<__remove_cv_t<_Tp>, bool>::value, "First argument to gcd cannot be bool");
static_assert(!is_same<__remove_cv_t<_Up>, bool>::value, "Second argument to gcd cannot be bool");
+ static_assert(__admits_bitint_extension_v<_Tp> && __admits_bitint_extension_v<_Up>,
+ "std::gcd does not accept _BitInt unless _LIBCPP_ENABLE_BITINT_EXTENSIONS is defined");
using _Rp = common_type_t<_Tp, _Up>;
using _Wp = make_unsigned_t<_Rp>;
@@ -99,6 +102,8 @@ constexpr _LIBCPP_HIDE_FROM_ABI common_type_t<_Tp, _Up> lcm(_Tp __m, _Up __n) {
static_assert(is_integral<_Tp>::value && is_integral<_Up>::value, "Arguments to lcm must be integer types");
static_assert(!is_same<__remove_cv_t<_Tp>, bool>::value, "First argument to lcm cannot be bool");
static_assert(!is_same<__remove_cv_t<_Up>, bool>::value, "Second argument to lcm cannot be bool");
+ static_assert(__admits_bitint_extension_v<_Tp> && __admits_bitint_extension_v<_Up>,
+ "std::lcm does not accept _BitInt unless _LIBCPP_ENABLE_BITINT_EXTENSIONS is defined");
if (__m == 0 || __n == 0)
return 0;
diff --git a/libcxx/include/__numeric/midpoint.h b/libcxx/include/__numeric/midpoint.h
index d8e73ab8cad36..ed98392f6e2bf 100644
--- a/libcxx/include/__numeric/midpoint.h
+++ b/libcxx/include/__numeric/midpoint.h
@@ -12,6 +12,7 @@
#include <__config>
#include <__cstddef/ptrdiff_t.h>
+#include <__type_traits/is_bit_precise_integer.h>
#include <__type_traits/is_floating_point.h>
#include <__type_traits/is_integral.h>
#include <__type_traits/is_object.h>
@@ -32,7 +33,7 @@ _LIBCPP_BEGIN_NAMESPACE_STD
#if _LIBCPP_STD_VER >= 20
template <class _Tp>
- requires(is_integral_v<_Tp> && !is_same_v<remove_cv_t<_Tp>, bool>)
+ requires(is_integral_v<_Tp> && !is_same_v<remove_cv_t<_Tp>, bool> && __admits_bitint_extension_v<_Tp>)
[[nodiscard]]
_LIBCPP_HIDE_FROM_ABI constexpr _Tp midpoint(_Tp __a, _Tp __b) noexcept _LIBCPP_DISABLE_UBSAN_UNSIGNED_INTEGER_CHECK {
using _Up = make_unsigned_t<_Tp>;
diff --git a/libcxx/include/__type_traits/integer_traits.h b/libcxx/include/__type_traits/integer_traits.h
index a0fbcd5c3ecd7..e0a5191fb0a76 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,16 @@ inline const bool __is_character_v<char16_t> = true;
template <>
inline const bool __is_character_v<char32_t> = true;
+// _BitInt participates only with _LIBCPP_HAS_BITINT_EXTENSIONS on (P3666R4).
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..96f9f8fa66671
--- /dev/null
+++ b/libcxx/include/__type_traits/is_bit_precise_integer.h
@@ -0,0 +1,43 @@
+//===----------------------------------------------------------------------===//
+//
+// 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 stripped) is _BitInt(N) or unsigned _BitInt(N).
+
+template <class _Tp>
+inline const bool __is_bit_precise_integer_impl_v = false;
+
+#ifdef __BITINT_MAXWIDTH__
+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> >;
+
+// Gate predicate: standard integers always admit; bit-precise integers admit
+// only with _LIBCPP_HAS_BITINT_EXTENSIONS 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..57d75a5993941
--- /dev/null
+++ b/libcxx/test/libcxx/bitint/extensions_disabled.verify.cpp
@@ -0,0 +1,153 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// Default build (no _LIBCPP_ENABLE_BITINT_EXTENSIONS): libc++ rejects _BitInt
+// operands in <bit>, <charconv>, <format>, std::hash, std::cmp_*. Type traits
+// and numeric_limits stay accepted per P3666R4.
+
+#include <bit>
+#include <charconv>
+#include <format>
+#include <functional>
+#include <numeric>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+#ifdef _LIBCPP_ENABLE_BITINT_EXTENSIONS
+# error "this test runs under the default (macro undefined)"
+#endif
+
+#ifndef __BITINT_MAXWIDTH__
+// expected-no-diagnostics
+#else
+
+using S = _BitInt(64);
+using U = unsigned _BitInt(64);
+
+// Canary: the trait plumbing is wired. If a future change breaks the trait or
+// the macro propagation, these assertions catch it before the rest of the
+// test, with a clear message.
+static_assert(std::__is_bit_precise_integer_v<U>);
+static_assert(std::__is_bit_precise_integer_v<S>);
+static_assert(!std::__is_bit_precise_integer_v<int>);
+static_assert(!std::__admits_bitint_extension_v<U>);
+static_assert(std::__admits_bitint_extension_v<int>);
+
+void test_byteswap(U u, S s) {
+ U cu = 0;
+ (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 test_hash(U u, S s) {
+ // The primary __hash_impl has = delete'd special members under default.
+ std::hash<U> hu; // expected-error@*:* {{implicitly-deleted default constructor}}
+ std::hash<S> hs; // expected-error@*:* {{implicitly-deleted default constructor}}
+ (void)hu;
+ (void)hs;
+ (void)u;
+ (void)s;
+}
+
+void test_charconv(char* b, char* e, U u, S s) {
+ // to_chars: integral templates disabled, falls to the floating-point overloads -> ambiguous.
+ (void)std::to_chars(b, e, u); // expected-error@*:* {{call to 'to_chars' is ambiguous}}
+ (void)std::to_chars(b, e, s); // expected-error@*:* {{call to 'to_chars' is ambiguous}}
+ // from_chars: integral templates disabled, falls to the deleted bool overload.
+ (void)std::from_chars(b, e, u); // expected-error@*:* {{call to deleted function 'from_chars'}}
+ (void)std::from_chars(b, e, s); // expected-error@*:* {{call to deleted function 'from_chars'}}
+}
+
+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_equal(s, 0); // expected-error@*:* {{no matching function for call to 'cmp_equal'}}
+ (void)std::cmp_greater(u, 0); // expected-error@*:* {{no matching function for call to 'cmp_greater'}}
+ (void)std::in_range<U>(0); // expected-error@*:* {{no matching function for call to 'in_range'}}
+}
+
+// std::format's _BitInt rejection diagnoses deep in formatter selection; the
+// SFINAE check at the bottom of this file verifies it without binding to a
+// specific diagnostic string.
+
+// gcd, lcm, midpoint are exercised by the SFINAE probes below (midpoint) and
+// by direct instantiation expectations during macro-on/off CI runs (gcd/lcm
+// reject via static_assert, which fires only on instantiation).
+
+// Surface P3666R4 keeps.
+static_assert(std::is_integral_v<U>);
+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 probes: detection idioms must return false, not hard-error. Widths
+// span sub-byte (1), byte-aligned (8/32/64), sub-byte-padded (13/65), and a
+// wide signed/unsigned mix.
+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_from_chars = requires(char* b, char* e, T t) { std::from_chars(b, e, t); };
+template <class T>
+concept has_cmp_less = requires(T t) { std::cmp_less(t, 0); };
+template <class T>
+concept has_midpoint = requires(T t) { std::midpoint(t, t); };
+// std::format / std::gcd / std::lcm reject via static_assert, not SFINAE.
+
+// Clang requires signed _BitInt(N) with N >= 2; only the unsigned form is
+// well-formed at N == 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 rejected too.
+static_assert(!has_byteswap<const _BitInt(64)>);
+static_assert(!has_byteswap<const unsigned _BitInt(64)>);
+
+// Standard integers still flow.
+static_assert(has_byteswap<int>);
+static_assert(has_byteswap<unsigned long long>);
+static_assert(has_byteswap<bool>);
+
+// Other gated facilities, same probe.
+static_assert(!has_hash<U>);
+static_assert(!has_hash<_BitInt(8)>);
+static_assert(!has_hash<_BitInt(128)>);
+static_assert(!has_to_chars<U>);
+static_assert(!has_to_chars<_BitInt(8)>);
+static_assert(!has_from_chars<U>);
+static_assert(!has_cmp_less<U>);
+static_assert(!has_cmp_less<_BitInt(8)>);
+
+static_assert(!has_midpoint<U>);
+static_assert(!has_midpoint<_BitInt(8)>);
+
+static_assert(has_hash<int>);
+static_assert(has_to_chars<int>);
+static_assert(has_from_chars<int>);
+static_assert(has_cmp_less<int>);
+static_assert(has_midpoint<int>);
+
+#endif // __BITINT_MAXWIDTH__
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..dfd0b7d16b48e
--- /dev/null
+++ b/libcxx/test/libcxx/bitint/extensions_enabled.compile.pass.cpp
@@ -0,0 +1,51 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+
+// With the macro on, libc++'s gated facilities accept _BitInt operands.
+
+#include <bit>
+#include <charconv>
+#include <functional>
+#include <type_traits>
+#include <utility>
+
+#include "test_macros.h"
+
+#ifdef __BITINT_MAXWIDTH__
+
+static_assert(std::__admits_bitint_extension_v<_BitInt(64)>);
+static_assert(std::__admits_bitint_extension_v<unsigned _BitInt(64)>);
+
+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); };
+
+using S64 = _BitInt(64);
+using U64 = unsigned _BitInt(64);
+using U8 = unsigned _BitInt(8);
+
+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_cmp_less<U64>);
+
+// Type-trait surface is macro-independent (P3666 keeps it).
+static_assert(std::is_integral_v<U64>);
+
+#endif // __BITINT_MAXWIDTH__
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