[libcxx-commits] [libcxx] [libc++] Implement `<bit>` parts of P3793R2 Better shifting (PR #219389)
Jan Schultke via libcxx-commits
libcxx-commits at lists.llvm.org
Thu Aug 27 23:58:13 PDT 2026
https://github.com/eisenwave updated https://github.com/llvm/llvm-project/pull/219389
>From f7c5ff334aa14149a2f88027f2eea01d2c82e005 Mon Sep 17 00:00:00 2001
From: Eisenwave <me at eisenwave.net>
Date: Fri, 28 Aug 2026 08:57:58 +0200
Subject: [PATCH] [libc++] Implement <bit> parts of P3793R2 Better shifting
---
libcxx/docs/FeatureTestMacroTable.rst | 2 +
libcxx/docs/ReleaseNotes/24.rst | 1 +
libcxx/docs/Status/Cxx29Papers.csv | 2 +-
libcxx/include/__bit/shift.h | 59 ++++
libcxx/include/bit | 10 +
libcxx/include/version | 5 +-
.../bit.version.compile.pass.cpp | 4 +-
.../version.version.compile.pass.cpp | 4 +-
.../numerics/bit/bitops.shift/shl.pass.cpp | 299 +++++++++++++++++
.../numerics/bit/bitops.shift/shr.pass.cpp | 302 ++++++++++++++++++
.../generate_feature_test_macro_components.py | 2 +-
11 files changed, 683 insertions(+), 7 deletions(-)
create mode 100644 libcxx/include/__bit/shift.h
create mode 100644 libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
create mode 100644 libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
diff --git a/libcxx/docs/FeatureTestMacroTable.rst b/libcxx/docs/FeatureTestMacroTable.rst
index cabd30847b2af..ea9157141cef6 100644
--- a/libcxx/docs/FeatureTestMacroTable.rst
+++ b/libcxx/docs/FeatureTestMacroTable.rst
@@ -558,6 +558,8 @@ Status
---------------------------------------------------------- -----------------
**C++29**
----------------------------------------------------------------------------
+ ``__cpp_lib_bitops`` ``202606L``
+ ---------------------------------------------------------- -----------------
``__cpp_lib_map_lookup`` *unimplemented*
---------------------------------------------------------- -----------------
``__cpp_lib_mdspan_copy`` *unimplemented*
diff --git a/libcxx/docs/ReleaseNotes/24.rst b/libcxx/docs/ReleaseNotes/24.rst
index 9cf08b5732970..504843122778e 100644
--- a/libcxx/docs/ReleaseNotes/24.rst
+++ b/libcxx/docs/ReleaseNotes/24.rst
@@ -47,6 +47,7 @@ Implemented Papers
- P3961R1: Less double indirection in ``function_ref`` (RU-220) (`Github <https://llvm.org/PR189606>`__)
- P1901R2: Enabling the Use of ``weak_ptr`` as Keys in Unordered Associative Containers (`Github <https://llvm.org/PR105372>`__)
- P0528R3: The Curious Case of Padding Bits, Featuring Atomic Compare-and-Exchange (`Github <https://llvm.org/PR76180>`__)
+- P3793R2: Better shifting (`Github <https://llvm.org/PR219389>`__)
Improvements and New Features
-----------------------------
diff --git a/libcxx/docs/Status/Cxx29Papers.csv b/libcxx/docs/Status/Cxx29Papers.csv
index 3c48fe89be33d..30f0588b49e60 100644
--- a/libcxx/docs/Status/Cxx29Papers.csv
+++ b/libcxx/docs/Status/Cxx29Papers.csv
@@ -11,7 +11,7 @@
"`P3154R3 <https://wg21.link/P3154R3>`__","Deprecating signed character types in iostreams","2026-06 (Brno)","","","`#204398 <https://github.com/llvm/llvm-project/issues/204398>`__",""
"`P3428R4 <https://wg21.link/P3428R4>`__","Hazard Pointer Batches","2026-06 (Brno)","","","`#204399 <https://github.com/llvm/llvm-project/issues/204399>`__",""
"`P3248R5 <https://wg21.link/P3248R5>`__","Require ``[u]intptr_t``","2026-06 (Brno)","","","`#204400 <https://github.com/llvm/llvm-project/issues/204400>`__",""
-"`P3793R2 <https://wg21.link/P3793R2>`__","Better shifting","2026-06 (Brno)","","","`#204401 <https://github.com/llvm/llvm-project/issues/204401>`__",""
+"`P3793R2 <https://wg21.link/P3793R2>`__","Better shifting","2026-06 (Brno)","|Partial|","24","`#204401 <https://github.com/llvm/llvm-project/issues/204401>`__","No `std::simd` overloads provided yet."
"`P3242R4 <https://wg21.link/P3242R4>`__","Copy and fill for ``mdspan``","2026-06 (Brno)","","","`#204402 <https://github.com/llvm/llvm-project/issues/204402>`__",""
"`P3692R4 <https://wg21.link/P3692R4>`__","How to Avoid OOTA Without Really Trying","2026-06 (Brno)","","","`#204403 <https://github.com/llvm/llvm-project/issues/204403>`__",""
"`P3104R6 <https://wg21.link/P3104R6>`__","Bit permutations","2026-06 (Brno)","","","`#204404 <https://github.com/llvm/llvm-project/issues/204404>`__",""
diff --git a/libcxx/include/__bit/shift.h b/libcxx/include/__bit/shift.h
new file mode 100644
index 0000000000000..4a9d7c8240976
--- /dev/null
+++ b/libcxx/include/__bit/shift.h
@@ -0,0 +1,59 @@
+//===----------------------------------------------------------------------===//
+//
+// 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___BIT_SHIFT_H
+#define _LIBCPP___BIT_SHIFT_H
+
+#include <__config>
+#include <__type_traits/integer_traits.h>
+#include <__type_traits/make_unsigned.h>
+#include <limits>
+
+#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
+# pragma GCC system_header
+#endif
+
+_LIBCPP_BEGIN_NAMESPACE_STD
+
+#if _LIBCPP_STD_VER >= 29
+
+template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Shift>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp shl(_Tp __t, _Shift __cnt) noexcept {
+ constexpr auto __n = static_cast<_Shift>(numeric_limits<make_unsigned_t<_Tp>>::digits);
+ if constexpr (is_signed_v<_Shift>) {
+ if (__cnt < 0) {
+ if (__cnt <= -__n)
+ return static_cast<_Tp>(__t < 0 ? -1 : 0);
+ return __t >> -__cnt;
+ }
+ }
+ if (__cnt >= __n)
+ return static_cast<_Tp>(0);
+ return __t << __cnt;
+}
+
+template <__signed_or_unsigned_integer _Tp, __signed_or_unsigned_integer _Shift>
+[[nodiscard]] _LIBCPP_HIDE_FROM_ABI constexpr _Tp shr(_Tp __t, _Shift __cnt) noexcept {
+ constexpr auto __n = static_cast<_Shift>(numeric_limits<make_unsigned_t<_Tp>>::digits);
+ if constexpr (is_signed_v<_Shift>) {
+ if (__cnt < 0) {
+ if (__cnt <= -__n)
+ return static_cast<_Tp>(0);
+ return __t << -__cnt;
+ }
+ }
+ if (__cnt >= __n)
+ return static_cast<_Tp>(__t < 0 ? -1 : 0);
+ return __t >> __cnt;
+}
+
+#endif // _LIBCPP_STD_VER >= 29
+
+_LIBCPP_END_NAMESPACE_STD
+
+#endif // _LIBCPP___BIT_SHIFT_H
diff --git a/libcxx/include/bit b/libcxx/include/bit
index 40fcda27856bb..be3dfdc451b3d 100644
--- a/libcxx/include/bit
+++ b/libcxx/include/bit
@@ -32,6 +32,12 @@ namespace std {
template <class T>
constexpr int bit_width(T x) noexcept; // C++20
+ // [bit.shift], shifting
+ template<class T, class S>
+ constexpr T shl(T x, S s) noexcept; // C++29
+ template<class T, class S>
+ constexpr T shr(T x, S s) noexcept; // C++29
+
// [bit.rotate], rotating
template<class T>
constexpr T rotl(T x, int s) noexcept; // C++20
@@ -84,6 +90,10 @@ namespace std {
# include <__bit/byteswap.h>
# endif
+# if _LIBCPP_STD_VER >= 29
+# include <__bit/shift.h>
+# endif
+
# include <version>
# if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
diff --git a/libcxx/include/version b/libcxx/include/version
index 589ce789ffa7c..421ceb20b270e 100644
--- a/libcxx/include/version
+++ b/libcxx/include/version
@@ -47,7 +47,8 @@ __cpp_lib_bind_back 202202L <functional>
__cpp_lib_bind_front 202306L <functional>
201907L // C++20
__cpp_lib_bit_cast 201806L <bit>
-__cpp_lib_bitops 201907L <bit>
+__cpp_lib_bitops 202606L <bit>
+ 201907L // C++20
__cpp_lib_bitset 202306L <bitset>
__cpp_lib_bool_constant 201505L <type_traits>
__cpp_lib_bounded_array_traits 201902L <type_traits>
@@ -661,6 +662,8 @@ __cpp_lib_void_t 201411L <type_traits>
#endif
#if _LIBCPP_STD_VER >= 29
+# undef __cpp_lib_bitops
+# define __cpp_lib_bitops 202606L
// # define __cpp_lib_map_lookup 202606L
// # define __cpp_lib_mdspan_copy 202606L
// # define __cpp_lib_pointer_tag_pair 202606L
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
index 39df8f7ed649e..28a7debf546fe 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/bit.version.compile.pass.cpp
@@ -204,8 +204,8 @@
# ifndef __cpp_lib_bitops
# error "__cpp_lib_bitops should be defined in c++29"
# endif
-# if __cpp_lib_bitops != 201907L
-# error "__cpp_lib_bitops should have the value 201907L in c++29"
+# if __cpp_lib_bitops != 202606L
+# error "__cpp_lib_bitops should have the value 202606L in c++29"
# endif
# ifndef __cpp_lib_byteswap
diff --git a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
index 769b79c7924d2..480b8692cfe5e 100644
--- a/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
+++ b/libcxx/test/std/language.support/support.limits/support.limits.general/version.version.compile.pass.cpp
@@ -8829,8 +8829,8 @@
# ifndef __cpp_lib_bitops
# error "__cpp_lib_bitops should be defined in c++29"
# endif
-# if __cpp_lib_bitops != 201907L
-# error "__cpp_lib_bitops should have the value 201907L in c++29"
+# if __cpp_lib_bitops != 202606L
+# error "__cpp_lib_bitops should have the value 202606L in c++29"
# endif
# ifndef __cpp_lib_bitset
diff --git a/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
new file mode 100644
index 0000000000000..511d20d2858fb
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
@@ -0,0 +1,299 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20, c++23, c++26
+
+// template <class T, class S>
+// constexpr T shl(T x, S s) noexcept;
+
+// Constraints: Each of T and S is a signed or unsigned integer type.
+
+#include <bit>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+constexpr bool test() {
+ ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+ using U = std::make_unsigned_t<T>;
+ constexpr int width = std::numeric_limits<U>::digits;
+
+ assert(std::shl(T(1), 0) == T(1));
+ assert(std::shl(T(1), 1) == T(2));
+ assert(std::shl(T(1), 2) == T(4));
+ assert(std::shl(T(1), 3) == T(8));
+ assert(std::shl(T(1), 4) == T(16));
+ assert(std::shl(T(1), 5) == T(32));
+ assert(std::shl(T(1), 6) == T(64));
+ assert(std::shl(T(1), 7) == T(128));
+
+ // Overlong shifts return 0
+ assert(std::shl(T(1), width) == T(0));
+ assert(std::shl(T(1), width + 1) == T(0));
+ assert(std::shl(T(1), width + 100) == T(0));
+ assert(std::shl(T(1), std::numeric_limits<int>::max()) == T(0));
+
+ // Test all-ones pattern shifted out
+ if constexpr (std::is_signed_v<T>) {
+ assert(std::shl(T(-1), width) == T(0));
+ assert(std::shl(T(-1), width + 5) == T(0));
+ } else {
+ assert(std::shl(T(~T(0)), width) == T(0));
+ assert(std::shl(T(~T(0)), width + 5) == T(0));
+ }
+
+ // Negative shift amounts shift right (bidirectional)
+ U highbit_u = U(1) << (width - 1);
+ T highbit = static_cast<T>(highbit_u);
+ assert(std::shl(highbit, -1) == static_cast<T>(highbit_u >> 1));
+ assert(std::shl(highbit, -2) == static_cast<T>(highbit_u >> 2));
+ assert(std::shl(highbit, -3) == static_cast<T>(highbit_u >> 3));
+ assert(std::shl(highbit, -4) == static_cast<T>(highbit_u >> 4));
+ assert(std::shl(highbit, -5) == static_cast<T>(highbit_u >> 5));
+ assert(std::shl(highbit, -6) == static_cast<T>(highbit_u >> 6));
+ assert(std::shl(highbit, -7) == static_cast<T>(highbit_u >> 7));
+ assert(std::shl(highbit, -(width - 1)) == T(1));
+
+ // Negative overlong shifts: 0 for unsigned, sign-extended for signed
+ if constexpr (std::is_signed_v<T>) {
+ assert(std::shl(T(-1), -width) == T(-1));
+ assert(std::shl(T(-1), -(width + 1)) == T(-1));
+ assert(std::shl(T(-1), std::numeric_limits<int>::min()) == T(-1));
+ assert(std::shl(T(42), -width) == T(0));
+ assert(std::shl(T(42), -(width + 1)) == T(0));
+ } else {
+ assert(std::shl(T(~T(0)), -width) == T(0));
+ assert(std::shl(T(~T(0)), -(width + 1)) == T(0));
+ assert(std::shl(T(1), std::numeric_limits<int>::min()) == T(0));
+ }
+
+#ifndef TEST_HAS_NO_INT128
+ if constexpr (std::is_same_v<T, __uint128_t>) {
+ assert(std::shl(T(1), 0) == T(1));
+ assert(std::shl(T(1), 64) == T(1) << 64);
+ assert(std::shl(T(1), 127) == T(1) << 127);
+ assert(std::shl(T(1), 128) == T(0));
+ assert(std::shl(T(1), 200) == T(0));
+ assert(std::shl(T(1) << 127, -1) == T(1) << 126);
+ assert(std::shl(T(1) << 127, -127) == T(1));
+ assert(std::shl(T(1) << 127, -128) == T(0));
+ assert(std::shl(T(1) << 127, -200) == T(0));
+ }
+#endif
+
+ return true;
+}
+
+int main(int, char**) {
+ {
+ auto lambda = [](auto x) -> decltype(std::shl(x, 1U)) {};
+ using L = decltype(lambda);
+
+ static_assert(std::is_invocable_v<L, signed char>);
+ static_assert(std::is_invocable_v<L, short>);
+ static_assert(std::is_invocable_v<L, int>);
+ static_assert(std::is_invocable_v<L, long>);
+ static_assert(std::is_invocable_v<L, long long>);
+#ifndef TEST_HAS_NO_INT128
+ static_assert(std::is_invocable_v<L, __int128_t>);
+#endif
+
+ static_assert(std::is_invocable_v<L, std::int8_t>);
+ static_assert(std::is_invocable_v<L, std::int16_t>);
+ static_assert(std::is_invocable_v<L, std::int32_t>);
+ static_assert(std::is_invocable_v<L, std::int64_t>);
+ static_assert(std::is_invocable_v<L, std::intmax_t>);
+ static_assert(std::is_invocable_v<L, std::intptr_t>);
+ static_assert(std::is_invocable_v<L, std::ptrdiff_t>);
+
+ static_assert(std::is_invocable_v<L, unsigned char>);
+ static_assert(std::is_invocable_v<L, unsigned short>);
+ static_assert(std::is_invocable_v<L, unsigned int>);
+ static_assert(std::is_invocable_v<L, unsigned long>);
+ static_assert(std::is_invocable_v<L, unsigned long long>);
+
+ static_assert(!std::is_invocable_v<L, bool>);
+ static_assert(!std::is_invocable_v<L, char>);
+ static_assert(!std::is_invocable_v<L, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+ static_assert(!std::is_invocable_v<L, char8_t>);
+#endif
+ static_assert(!std::is_invocable_v<L, char16_t>);
+ static_assert(!std::is_invocable_v<L, char32_t>);
+
+ static_assert(!std::is_invocable_v<L, A>);
+ static_assert(!std::is_invocable_v<L, A*>);
+ static_assert(!std::is_invocable_v<L, E1>);
+ static_assert(!std::is_invocable_v<L, E2>);
+ }
+ {
+ auto lambda_s = [](auto s) -> decltype(std::shl(1, s)) {};
+ using LS = decltype(lambda_s);
+
+ static_assert(std::is_invocable_v<LS, signed char>);
+ static_assert(std::is_invocable_v<LS, short>);
+ static_assert(std::is_invocable_v<LS, int>);
+ static_assert(std::is_invocable_v<LS, long>);
+ static_assert(std::is_invocable_v<LS, long long>);
+
+ static_assert(std::is_invocable_v<LS, unsigned char>);
+ static_assert(std::is_invocable_v<LS, unsigned short>);
+ static_assert(std::is_invocable_v<LS, unsigned int>);
+ static_assert(std::is_invocable_v<LS, unsigned long>);
+ static_assert(std::is_invocable_v<LS, unsigned long long>);
+
+ static_assert(!std::is_invocable_v<LS, bool>);
+ static_assert(!std::is_invocable_v<LS, char>);
+ static_assert(!std::is_invocable_v<LS, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+ static_assert(!std::is_invocable_v<LS, char8_t>);
+#endif
+ static_assert(!std::is_invocable_v<LS, char16_t>);
+ static_assert(!std::is_invocable_v<LS, char32_t>);
+
+ static_assert(!std::is_invocable_v<LS, A>);
+ static_assert(!std::is_invocable_v<LS, A*>);
+ static_assert(!std::is_invocable_v<LS, E1>);
+ static_assert(!std::is_invocable_v<LS, E2>);
+ }
+
+ static_assert(test<unsigned char>());
+ static_assert(test<unsigned short>());
+ static_assert(test<unsigned int>());
+ static_assert(test<unsigned long>());
+ static_assert(test<unsigned long long>());
+#ifndef TEST_HAS_NO_INT128
+ static_assert(test<__uint128_t>());
+#endif
+
+ static_assert(test<signed char>());
+ static_assert(test<short>());
+ static_assert(test<int>());
+ static_assert(test<long>());
+ static_assert(test<long long>());
+
+ static_assert(test<std::uint8_t>());
+ static_assert(test<std::uint16_t>());
+ static_assert(test<std::uint32_t>());
+ static_assert(test<std::uint64_t>());
+ static_assert(test<std::uintmax_t>());
+ static_assert(test<std::uintptr_t>());
+ static_assert(test<std::size_t>());
+
+ static_assert(test<std::int8_t>());
+ static_assert(test<std::int16_t>());
+ static_assert(test<std::int32_t>());
+ static_assert(test<std::int64_t>());
+ static_assert(test<std::intmax_t>());
+ static_assert(test<std::intptr_t>());
+ static_assert(test<std::ptrdiff_t>());
+
+ test<unsigned char>();
+ test<unsigned short>();
+ test<unsigned int>();
+ test<unsigned long>();
+ test<unsigned long long>();
+#ifndef TEST_HAS_NO_INT128
+ test<__uint128_t>();
+#endif
+
+ test<signed char>();
+ test<short>();
+ test<int>();
+ test<long>();
+ test<long long>();
+
+ test<std::uint8_t>();
+ test<std::uint16_t>();
+ test<std::uint32_t>();
+ test<std::uint64_t>();
+ test<std::uintmax_t>();
+ test<std::uintptr_t>();
+ test<std::size_t>();
+
+ test<std::int8_t>();
+ test<std::int16_t>();
+ test<std::int32_t>();
+ test<std::int64_t>();
+ test<std::intmax_t>();
+ test<std::intptr_t>();
+ test<std::ptrdiff_t>();
+
+#if TEST_HAS_EXTENSION(bit_int)
+ {
+ using T32 = unsigned _BitInt(32);
+ using T64 = unsigned _BitInt(64);
+
+ assert(std::shl(T32(1), 0) == T32(1));
+ assert(std::shl(T32(1), 1) == T32(2));
+ assert(std::shl(T32(1), 4) == T32(16));
+ assert(std::shl(T32(1), 7) == T32(128));
+ assert(std::shl(T32(1), 31) == T32(1) << 31);
+ // Overlong shift returns 0
+ assert(std::shl(T32(1), 32) == T32(0));
+ assert(std::shl(T32(~T32(0)), 32) == T32(0));
+ // Negative shift (bidirectional)
+ assert(std::shl(T32(1) << 31, -1) == T32(1) << 30);
+ assert(std::shl(T32(1) << 31, -31) == T32(1));
+ assert(std::shl(T32(1) << 31, -32) == T32(0));
+
+ assert(std::shl(T64(1), 0) == T64(1));
+ assert(std::shl(T64(1), 4) == T64(16));
+ assert(std::shl(T64(1), 63) == T64(1) << 63);
+ assert(std::shl(T64(1), 64) == T64(0));
+ assert(std::shl(T64(1) << 63, -1) == T64(1) << 62);
+ assert(std::shl(T64(1) << 63, -63) == T64(1));
+ assert(std::shl(T64(1) << 63, -64) == T64(0));
+ }
+# if __BITINT_MAXWIDTH__ >= 128
+ {
+ using T128 = unsigned _BitInt(128);
+ assert(std::shl(T128(1), 0) == T128(1));
+ assert(std::shl(T128(1), 4) == T128(16));
+ assert(std::shl(T128(1), 63) == T128(1) << 63);
+ assert(std::shl(T128(1), 64) == T128(1) << 64);
+ assert(std::shl(T128(1), 127) == T128(1) << 127);
+ // Overlong shifts return 0
+ assert(std::shl(T128(1), 128) == T128(0));
+ assert(std::shl(T128(1), 200) == T128(0));
+ // Negative shift (bidirectional)
+ assert(std::shl(T128(1) << 127, -1) == T128(1) << 126);
+ assert(std::shl(T128(1) << 127, -127) == T128(1));
+ assert(std::shl(T128(1) << 127, -128) == T128(0));
+ }
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
+ {
+ using T256 = unsigned _BitInt(256);
+ assert(std::shl(T256(1), 0) == T256(1));
+ assert(std::shl(T256(1), 4) == T256(16));
+ assert(std::shl(T256(1), 200) == T256(1) << 200);
+ // Overlong shift returns 0
+ assert(std::shl(T256(1), 256) == T256(0));
+ assert(std::shl(T256(1), 300) == T256(0));
+ // Negative shift (bidirectional)
+ assert(std::shl(T256(1) << 255, -1) == T256(1) << 254);
+ assert(std::shl(T256(1) << 255, -255) == T256(1));
+ assert(std::shl(T256(1) << 255, -256) == T256(0));
+ }
+# endif
+#endif // TEST_HAS_EXTENSION(bit_int)
+
+ return 0;
+}
diff --git a/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp b/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
new file mode 100644
index 0000000000000..0f1000c9c639e
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
@@ -0,0 +1,302 @@
+//===----------------------------------------------------------------------===//
+//
+// 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, c++20, c++23, c++26
+
+// template <class T, class S>
+// constexpr T shr(T x, S s) noexcept;
+
+// Constraints: Each of T and S is a signed or unsigned integer type.
+
+#include <bit>
+#include <cassert>
+#include <cstddef>
+#include <cstdint>
+#include <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+constexpr bool test() {
+ ASSERT_SAME_TYPE(decltype(std::shr(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shr(T(), 0));
+
+ using U = std::make_unsigned_t<T>;
+ constexpr int width = std::numeric_limits<U>::digits;
+ constexpr U highbit_u = U(1) << (width - 1);
+
+ T highbit = static_cast<T>(highbit_u);
+ assert(std::shr(highbit, 0) == highbit);
+ assert(std::shr(highbit, 1) == static_cast<T>(highbit_u >> 1));
+ assert(std::shr(highbit, 2) == static_cast<T>(highbit_u >> 2));
+ assert(std::shr(highbit, 3) == static_cast<T>(highbit_u >> 3));
+ assert(std::shr(highbit, 4) == static_cast<T>(highbit_u >> 4));
+ assert(std::shr(highbit, 5) == static_cast<T>(highbit_u >> 5));
+ assert(std::shr(highbit, 6) == static_cast<T>(highbit_u >> 6));
+ assert(std::shr(highbit, 7) == static_cast<T>(highbit_u >> 7));
+ assert(std::shr(highbit, width - 1) == T(1));
+
+ // Overlong right shifts: 0 for unsigned, sign-extended for signed
+ if constexpr (std::is_signed_v<T>) {
+ assert(std::shr(T(-1), width) == T(-1));
+ assert(std::shr(T(-1), width + 1) == T(-1));
+ assert(std::shr(T(-1), std::numeric_limits<int>::max()) == T(-1));
+ assert(std::shr(T(42), width) == T(0));
+ assert(std::shr(T(42), width + 1) == T(0));
+ } else {
+ assert(std::shr(T(~T(0)), width) == T(0));
+ assert(std::shr(T(~T(0)), width + 1) == T(0));
+ assert(std::shr(T(~T(0)), std::numeric_limits<int>::max()) == T(0));
+ }
+
+ // Negative shift amounts shift left (bidirectional)
+ assert(std::shr(T(1), -1) == static_cast<T>(U(1) << 1));
+ assert(std::shr(T(1), -2) == static_cast<T>(U(1) << 2));
+ assert(std::shr(T(1), -3) == static_cast<T>(U(1) << 3));
+ assert(std::shr(T(1), -4) == static_cast<T>(U(1) << 4));
+ assert(std::shr(T(1), -5) == static_cast<T>(U(1) << 5));
+ assert(std::shr(T(1), -6) == static_cast<T>(U(1) << 6));
+ assert(std::shr(T(1), -7) == static_cast<T>(U(1) << 7));
+ assert(std::shr(T(1), -(width - 1)) == static_cast<T>(U(1) << (width - 1)));
+
+ // Negative overlong shifts: always return 0
+ if constexpr (std::is_signed_v<T>) {
+ assert(std::shr(T(-1), -width) == T(0));
+ assert(std::shr(T(-1), -(width + 1)) == T(0));
+ assert(std::shr(T(-1), std::numeric_limits<int>::min()) == T(0));
+ } else {
+ assert(std::shr(T(~T(0)), -width) == T(0));
+ assert(std::shr(T(~T(0)), -(width + 1)) == T(0));
+ }
+ assert(std::shr(T(1), std::numeric_limits<int>::min()) == T(0));
+
+#ifndef TEST_HAS_NO_INT128
+ if constexpr (std::is_same_v<T, __uint128_t>) {
+ assert(std::shr(T(1) << 127, 0) == T(1) << 127);
+ assert(std::shr(T(1) << 127, 1) == T(1) << 126);
+ assert(std::shr(T(1) << 127, 64) == T(1) << 63);
+ assert(std::shr(T(1) << 127, 127) == T(1));
+ assert(std::shr(T(1) << 127, 128) == T(0));
+ assert(std::shr(T(1) << 127, 200) == T(0));
+ assert(std::shr(T(1), -1) == T(2));
+ assert(std::shr(T(1), -64) == T(1) << 64);
+ assert(std::shr(T(1), -127) == T(1) << 127);
+ assert(std::shr(T(1), -128) == T(0));
+ assert(std::shr(T(1), -200) == T(0));
+ }
+#endif
+
+ return true;
+}
+
+int main(int, char**) {
+ {
+ auto lambda = [](auto x) -> decltype(std::shr(x, 1U)) {};
+ using L = decltype(lambda);
+
+ static_assert(std::is_invocable_v<L, signed char>);
+ static_assert(std::is_invocable_v<L, short>);
+ static_assert(std::is_invocable_v<L, int>);
+ static_assert(std::is_invocable_v<L, long>);
+ static_assert(std::is_invocable_v<L, long long>);
+#ifndef TEST_HAS_NO_INT128
+ static_assert(std::is_invocable_v<L, __int128_t>);
+#endif
+
+ static_assert(std::is_invocable_v<L, std::int8_t>);
+ static_assert(std::is_invocable_v<L, std::int16_t>);
+ static_assert(std::is_invocable_v<L, std::int32_t>);
+ static_assert(std::is_invocable_v<L, std::int64_t>);
+ static_assert(std::is_invocable_v<L, std::intmax_t>);
+ static_assert(std::is_invocable_v<L, std::intptr_t>);
+ static_assert(std::is_invocable_v<L, std::ptrdiff_t>);
+
+ static_assert(std::is_invocable_v<L, unsigned char>);
+ static_assert(std::is_invocable_v<L, unsigned short>);
+ static_assert(std::is_invocable_v<L, unsigned int>);
+ static_assert(std::is_invocable_v<L, unsigned long>);
+ static_assert(std::is_invocable_v<L, unsigned long long>);
+
+ static_assert(!std::is_invocable_v<L, bool>);
+ static_assert(!std::is_invocable_v<L, char>);
+ static_assert(!std::is_invocable_v<L, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+ static_assert(!std::is_invocable_v<L, char8_t>);
+#endif
+ static_assert(!std::is_invocable_v<L, char16_t>);
+ static_assert(!std::is_invocable_v<L, char32_t>);
+
+ static_assert(!std::is_invocable_v<L, A>);
+ static_assert(!std::is_invocable_v<L, A*>);
+ static_assert(!std::is_invocable_v<L, E1>);
+ static_assert(!std::is_invocable_v<L, E2>);
+ }
+ {
+ auto lambda_s = [](auto s) -> decltype(std::shr(1u, s)) {};
+ using LS = decltype(lambda_s);
+
+ static_assert(std::is_invocable_v<LS, signed char>);
+ static_assert(std::is_invocable_v<LS, short>);
+ static_assert(std::is_invocable_v<LS, int>);
+ static_assert(std::is_invocable_v<LS, long>);
+ static_assert(std::is_invocable_v<LS, long long>);
+
+ static_assert(std::is_invocable_v<LS, unsigned char>);
+ static_assert(std::is_invocable_v<LS, unsigned short>);
+ static_assert(std::is_invocable_v<LS, unsigned int>);
+ static_assert(std::is_invocable_v<LS, unsigned long>);
+ static_assert(std::is_invocable_v<LS, unsigned long long>);
+
+ static_assert(!std::is_invocable_v<LS, bool>);
+ static_assert(!std::is_invocable_v<LS, char>);
+ static_assert(!std::is_invocable_v<LS, wchar_t>);
+#ifndef TEST_HAS_NO_CHAR8_T
+ static_assert(!std::is_invocable_v<LS, char8_t>);
+#endif
+ static_assert(!std::is_invocable_v<LS, char16_t>);
+ static_assert(!std::is_invocable_v<LS, char32_t>);
+
+ static_assert(!std::is_invocable_v<LS, A>);
+ static_assert(!std::is_invocable_v<LS, A*>);
+ static_assert(!std::is_invocable_v<LS, E1>);
+ static_assert(!std::is_invocable_v<LS, E2>);
+ }
+
+ static_assert(test<unsigned char>());
+ static_assert(test<unsigned short>());
+ static_assert(test<unsigned int>());
+ static_assert(test<unsigned long>());
+ static_assert(test<unsigned long long>());
+#ifndef TEST_HAS_NO_INT128
+ static_assert(test<__uint128_t>());
+#endif
+
+ static_assert(test<signed char>());
+ static_assert(test<short>());
+ static_assert(test<int>());
+ static_assert(test<long>());
+ static_assert(test<long long>());
+
+ static_assert(test<std::uint8_t>());
+ static_assert(test<std::uint16_t>());
+ static_assert(test<std::uint32_t>());
+ static_assert(test<std::uint64_t>());
+ static_assert(test<std::uintmax_t>());
+ static_assert(test<std::uintptr_t>());
+ static_assert(test<std::size_t>());
+
+ static_assert(test<std::int8_t>());
+ static_assert(test<std::int16_t>());
+ static_assert(test<std::int32_t>());
+ static_assert(test<std::int64_t>());
+ static_assert(test<std::intmax_t>());
+ static_assert(test<std::intptr_t>());
+ static_assert(test<std::ptrdiff_t>());
+
+ test<unsigned char>();
+ test<unsigned short>();
+ test<unsigned int>();
+ test<unsigned long>();
+ test<unsigned long long>();
+#ifndef TEST_HAS_NO_INT128
+ test<__uint128_t>();
+#endif
+
+ test<signed char>();
+ test<short>();
+ test<int>();
+ test<long>();
+ test<long long>();
+
+ test<std::uint8_t>();
+ test<std::uint16_t>();
+ test<std::uint32_t>();
+ test<std::uint64_t>();
+ test<std::uintmax_t>();
+ test<std::uintptr_t>();
+ test<std::size_t>();
+
+ test<std::int8_t>();
+ test<std::int16_t>();
+ test<std::int32_t>();
+ test<std::int64_t>();
+ test<std::intmax_t>();
+ test<std::intptr_t>();
+ test<std::ptrdiff_t>();
+
+#if TEST_HAS_EXTENSION(bit_int)
+ {
+ using T32 = unsigned _BitInt(32);
+ using T64 = unsigned _BitInt(64);
+
+ assert(std::shr(T32(1) << 31, 0) == T32(1) << 31);
+ assert(std::shr(T32(1) << 31, 1) == T32(1) << 30);
+ assert(std::shr(T32(1) << 31, 4) == T32(1) << 27);
+ assert(std::shr(T32(1) << 31, 7) == T32(1) << 24);
+ assert(std::shr(T32(1) << 31, 31) == T32(1));
+ // Overlong shift
+ assert(std::shr(T32(1) << 31, 32) == T32(0));
+ assert(std::shr(T32(~T32(0)), 32) == T32(0));
+ // Negative shift (bidirectional)
+ assert(std::shr(T32(1), -1) == T32(2));
+ assert(std::shr(T32(1), -4) == T32(16));
+ assert(std::shr(T32(1), -31) == T32(1) << 31);
+ assert(std::shr(T32(1), -32) == T32(0));
+
+ assert(std::shr(T64(1) << 63, 0) == T64(1) << 63);
+ assert(std::shr(T64(1) << 63, 1) == T64(1) << 62);
+ assert(std::shr(T64(1) << 63, 4) == T64(1) << 59);
+ assert(std::shr(T64(1) << 63, 63) == T64(1));
+ assert(std::shr(T64(1) << 63, 64) == T64(0));
+ assert(std::shr(T64(1), -1) == T64(2));
+ assert(std::shr(T64(1), -4) == T64(16));
+ assert(std::shr(T64(1), -63) == T64(1) << 63);
+ assert(std::shr(T64(1), -64) == T64(0));
+ }
+# if __BITINT_MAXWIDTH__ >= 128
+ {
+ using T128 = unsigned _BitInt(128);
+ assert(std::shr(T128(1) << 127, 0) == T128(1) << 127);
+ assert(std::shr(T128(1) << 127, 1) == T128(1) << 126);
+ assert(std::shr(T128(1) << 127, 64) == T128(1) << 63);
+ assert(std::shr(T128(1) << 127, 127) == T128(1));
+ // Overlong shifts return 0
+ assert(std::shr(T128(1) << 127, 128) == T128(0));
+ assert(std::shr(T128(1) << 127, 200) == T128(0));
+ // Negative shift (bidirectional)
+ assert(std::shr(T128(1), -1) == T128(2));
+ assert(std::shr(T128(1), -64) == T128(1) << 64);
+ assert(std::shr(T128(1), -127) == T128(1) << 127);
+ assert(std::shr(T128(1), -128) == T128(0));
+ }
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
+ {
+ using T256 = unsigned _BitInt(256);
+ assert(std::shr(T256(1) << 255, 0) == T256(1) << 255);
+ assert(std::shr(T256(1) << 255, 1) == T256(1) << 254);
+ assert(std::shr(T256(1) << 255, 200) == T256(1) << 55);
+ // Overlong shifts return 0
+ assert(std::shr(T256(1) << 255, 256) == T256(0));
+ assert(std::shr(T256(1) << 255, 300) == T256(0));
+ // Negative shift (bidirectional)
+ assert(std::shr(T256(1), -1) == T256(2));
+ assert(std::shr(T256(1), -200) == T256(1) << 200);
+ assert(std::shr(T256(1), -255) == T256(1) << 255);
+ assert(std::shr(T256(1), -256) == T256(0));
+ }
+# endif
+#endif // TEST_HAS_EXTENSION(bit_int)
+
+ return 0;
+}
diff --git a/libcxx/utils/generate_feature_test_macro_components.py b/libcxx/utils/generate_feature_test_macro_components.py
index 6b4acf671a0c9..8eb7f9779497d 100644
--- a/libcxx/utils/generate_feature_test_macro_components.py
+++ b/libcxx/utils/generate_feature_test_macro_components.py
@@ -248,7 +248,7 @@ def add_version_header(tc):
"name": "__cpp_lib_bitops",
"values": {
"c++20": 201907,
- # "c++29": 202606, # P3793R2 Better shifting
+ "c++29": 202606, # P3793R2 Better shifting
# "c++29": 202607, # P3104R6 Bit permutations
},
"headers": ["bit"],
More information about the libcxx-commits
mailing list