[libcxx-commits] [libcxx] [libc++] Implement `<bit>` parts of P3793R2 Better shifting (PR #219389)
Jan Schultke via libcxx-commits
libcxx-commits at lists.llvm.org
Fri Aug 28 01:01:09 PDT 2026
https://github.com/eisenwave updated https://github.com/llvm/llvm-project/pull/219389
>From 7db002381bf3cb16efebabba0d1aec725297c1d3 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/CMakeLists.txt | 1 +
libcxx/include/__bit/shift.h | 60 +++++
libcxx/include/bit | 10 +
libcxx/include/version | 5 +-
libcxx/modules/std/bit.inc | 4 +
.../libcxx/utilities/bit/nodiscard.verify.cpp | 6 +
.../bit.version.compile.pass.cpp | 4 +-
.../version.version.compile.pass.cpp | 4 +-
.../numerics/bit/bitops.shift/shl.pass.cpp | 209 +++++++++++++++++
.../numerics/bit/bitops.shift/shr.pass.cpp | 217 ++++++++++++++++++
.../generate_feature_test_macro_components.py | 2 +-
14 files changed, 520 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..ff774e636efc5 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>`__) (no ``std::simd`` overloads)
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/CMakeLists.txt b/libcxx/include/CMakeLists.txt
index f0caccdc94194..af3ac91e97c9a 100644
--- a/libcxx/include/CMakeLists.txt
+++ b/libcxx/include/CMakeLists.txt
@@ -244,6 +244,7 @@ set(files
__bit/invert_if.h
__bit/popcount.h
__bit/rotate.h
+ __bit/shift.h
__bit_reference
__charconv/chars_format.h
__charconv/from_chars_floating_point.h
diff --git a/libcxx/include/__bit/shift.h b/libcxx/include/__bit/shift.h
new file mode 100644
index 0000000000000..4011b7ad83757
--- /dev/null
+++ b/libcxx/include/__bit/shift.h
@@ -0,0 +1,60 @@
+//===----------------------------------------------------------------------===//
+//
+// 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);
+ } else if (__cnt >= __n)
+ return static_cast<_Tp>(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/modules/std/bit.inc b/libcxx/modules/std/bit.inc
index 027a6c17ad6a6..e0e845d8249c5 100644
--- a/libcxx/modules/std/bit.inc
+++ b/libcxx/modules/std/bit.inc
@@ -22,6 +22,10 @@ export namespace std {
using std::bit_width;
using std::has_single_bit;
+ // [bit.shift], shifting
+ using std::shl;
+ using std::shr;
+
// [bit.rotate], rotating
using std::rotl;
using std::rotr;
diff --git a/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp b/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
index 4d3c9e41b783e..6915728843717 100644
--- a/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
+++ b/libcxx/test/libcxx/utilities/bit/nodiscard.verify.cpp
@@ -31,6 +31,12 @@ void func() {
std::has_single_bit(0u); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::popcount(0u); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+#if TEST_STD_VER >= 29
+ // [bit.shift]
+ std::shl(1, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+ std::shr(1, 1); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
+#endif
+
// [bit.rotate]
std::rotl(0u, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
std::rotr(0u, 0); // expected-warning {{ignoring return value of function declared with 'nodiscard' attribute}}
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..0ffdcacb8d704
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shl.pass.cpp
@@ -0,0 +1,209 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++29
+
+// 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 <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+template <class T>
+constexpr bool test() {
+ using U = std::make_unsigned_t<T>;
+ constexpr int width = std::numeric_limits<U>::digits;
+ constexpr U highbit_u = U(1) << (width - 1);
+
+ if constexpr (std::is_signed_v<T>) {
+ ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+ 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) == static_cast<T>(U(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));
+
+ assert(std::shl(T(-1), width) == T(0));
+ assert(std::shl(T(-1), width + 5) == T(0));
+
+ // Negative shift amounts shift right arithmetically (bidirectional)
+ T highbit = static_cast<T>(highbit_u);
+ assert(std::shl(highbit, -1) == (highbit >> 1));
+ assert(std::shl(highbit, -2) == (highbit >> 2));
+ assert(std::shl(highbit, -3) == (highbit >> 3));
+ assert(std::shl(highbit, -4) == (highbit >> 4));
+ assert(std::shl(highbit, -5) == (highbit >> 5));
+ assert(std::shl(highbit, -6) == (highbit >> 6));
+ assert(std::shl(highbit, -7) == (highbit >> 7));
+ assert(std::shl(highbit, -(width - 1)) == (highbit >> (width - 1)));
+
+ // Negative overlong shifts: sign-extended for negative values, 0 otherwise
+ 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));
+
+ if constexpr (width == 128) {
+ assert(std::shl(highbit, -64) == (highbit >> 64));
+ assert(std::shl(highbit, -127) == (highbit >> 127));
+ assert(std::shl(highbit, -128) == T(-1));
+ assert(std::shl(highbit, -200) == T(-1));
+ } else if constexpr (width == 256) {
+ assert(std::shl(highbit, -64) == (highbit >> 64));
+ assert(std::shl(highbit, -127) == (highbit >> 127));
+ assert(std::shl(highbit, -128) == (highbit >> 128));
+ assert(std::shl(highbit, -200) == (highbit >> 200));
+ assert(std::shl(highbit, -255) == T(-1));
+ assert(std::shl(highbit, -256) == T(-1));
+ }
+ } else {
+ ASSERT_SAME_TYPE(decltype(std::shl(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shl(T(), 0));
+
+ 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));
+
+ 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)
+ T highbit = highbit_u;
+ assert(std::shl(highbit, -1) == (highbit_u >> 1));
+ assert(std::shl(highbit, -2) == (highbit_u >> 2));
+ assert(std::shl(highbit, -3) == (highbit_u >> 3));
+ assert(std::shl(highbit, -4) == (highbit_u >> 4));
+ assert(std::shl(highbit, -5) == (highbit_u >> 5));
+ assert(std::shl(highbit, -6) == (highbit_u >> 6));
+ assert(std::shl(highbit, -7) == (highbit_u >> 7));
+ assert(std::shl(highbit, -(width - 1)) == T(1));
+
+ // Negative overlong shifts: 0 for unsigned
+ 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));
+
+ if constexpr (width == 128) {
+ 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));
+ } else if constexpr (width == 256) {
+ 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(1) << 128);
+ assert(std::shl(T(1), 200) == T(1) << 200);
+ 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));
+ assert(std::shl(T(1), 256) == T(0));
+ assert(std::shl(T(1), 300) == T(0));
+ }
+ }
+
+ return true;
+}
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+concept can_shl_value = requires(T value) { std::shl(value, 1); };
+
+template <class T>
+concept can_shl_shift = requires(T shift) { std::shl(1, shift); };
+
+int main(int, char**) {
+ static_assert(!can_shl_value<bool>);
+ static_assert(!can_shl_value<char>);
+ static_assert(!can_shl_value<wchar_t>);
+ static_assert(!can_shl_value<char8_t>);
+ static_assert(!can_shl_value<char16_t>);
+ static_assert(!can_shl_value<char32_t>);
+ static_assert(!can_shl_value<A>);
+ static_assert(!can_shl_value<A*>);
+ static_assert(!can_shl_value<E1>);
+ static_assert(!can_shl_value<E2>);
+
+ static_assert(!can_shl_shift<bool>);
+ static_assert(!can_shl_shift<char>);
+ static_assert(!can_shl_shift<wchar_t>);
+ static_assert(!can_shl_shift<char8_t>);
+ static_assert(!can_shl_shift<char16_t>);
+ static_assert(!can_shl_shift<char32_t>);
+ static_assert(!can_shl_shift<A>);
+ static_assert(!can_shl_shift<A*>);
+ static_assert(!can_shl_shift<E1>);
+ static_assert(!can_shl_shift<E2>);
+
+ constexpr auto test_type = []<class T> {
+ static_assert(test<T>());
+ test<T>();
+ };
+
+ types::for_each(types::unsigned_integer_types{}, test_type);
+ types::for_each(types::signed_integer_types{}, test_type);
+
+#if TEST_HAS_BITINT
+ using bitint_types = types::type_list<
+ signed _BitInt(32),
+ unsigned _BitInt(32),
+ signed _BitInt(64),
+ unsigned _BitInt(64)
+# if __BITINT_MAXWIDTH__ >= 128
+ ,
+ signed _BitInt(128),
+ unsigned _BitInt(128)
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
+ ,
+ signed _BitInt(256),
+ unsigned _BitInt(256)
+# endif
+ >;
+ types::for_each(bitint_types{}, test_type);
+#endif
+
+ 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..b3501284bfd4d
--- /dev/null
+++ b/libcxx/test/std/numerics/bit/bitops.shift/shr.pass.cpp
@@ -0,0 +1,217 @@
+//===----------------------------------------------------------------------===//
+//
+// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
+// See https://llvm.org/LICENSE.txt for license information.
+// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
+//
+//===----------------------------------------------------------------------===//
+
+// REQUIRES: std-at-least-c++29
+
+// 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 <limits>
+#include <type_traits>
+
+#include "test_macros.h"
+#include "type_algorithms.h"
+
+template <class T>
+constexpr bool test() {
+ using U = std::make_unsigned_t<T>;
+ constexpr int width = std::numeric_limits<U>::digits;
+ constexpr U highbit_u = U(1) << (width - 1);
+
+ if constexpr (std::is_signed_v<T>) {
+ ASSERT_SAME_TYPE(decltype(std::shr(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shr(T(), 0));
+
+ T highbit = static_cast<T>(highbit_u);
+ assert(std::shr(highbit, 0) == highbit);
+ assert(std::shr(highbit, 1) == (highbit >> 1));
+ assert(std::shr(highbit, 2) == (highbit >> 2));
+ assert(std::shr(highbit, 3) == (highbit >> 3));
+ assert(std::shr(highbit, 4) == (highbit >> 4));
+ assert(std::shr(highbit, 5) == (highbit >> 5));
+ assert(std::shr(highbit, 6) == (highbit >> 6));
+ assert(std::shr(highbit, 7) == (highbit >> 7));
+ assert(std::shr(highbit, width - 1) == (highbit >> (width - 1)));
+
+ // Overlong right shifts sign-extend negative values and return 0 otherwise
+ 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));
+
+ // 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
+ 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));
+
+ if constexpr (width == 128) {
+ assert(std::shr(highbit, 64) == (highbit >> 64));
+ assert(std::shr(highbit, 127) == (highbit >> 127));
+ assert(std::shr(highbit, 128) == T(-1));
+ assert(std::shr(highbit, 200) == T(-1));
+ assert(std::shr(T(1), -64) == static_cast<T>(U(1) << 64));
+ assert(std::shr(T(1), -127) == static_cast<T>(U(1) << 127));
+ assert(std::shr(T(1), -128) == T(0));
+ assert(std::shr(T(1), -200) == T(0));
+ } else if constexpr (width == 256) {
+ assert(std::shr(highbit, 64) == (highbit >> 64));
+ assert(std::shr(highbit, 127) == (highbit >> 127));
+ assert(std::shr(highbit, 128) == (highbit >> 128));
+ assert(std::shr(highbit, 200) == (highbit >> 200));
+ assert(std::shr(highbit, 255) == T(-1));
+ assert(std::shr(highbit, 256) == T(-1));
+ assert(std::shr(T(1), -64) == static_cast<T>(U(1) << 64));
+ assert(std::shr(T(1), -127) == static_cast<T>(U(1) << 127));
+ assert(std::shr(T(1), -128) == static_cast<T>(U(1) << 128));
+ assert(std::shr(T(1), -200) == static_cast<T>(U(1) << 200));
+ assert(std::shr(T(1), -256) == T(0));
+ assert(std::shr(T(1), -300) == T(0));
+ }
+ } else {
+ ASSERT_SAME_TYPE(decltype(std::shr(T(), 0)), T);
+ ASSERT_NOEXCEPT(std::shr(T(), 0));
+
+ T highbit = highbit_u;
+ assert(std::shr(highbit, 0) == highbit);
+ assert(std::shr(highbit, 1) == (highbit_u >> 1));
+ assert(std::shr(highbit, 2) == (highbit_u >> 2));
+ assert(std::shr(highbit, 3) == (highbit_u >> 3));
+ assert(std::shr(highbit, 4) == (highbit_u >> 4));
+ assert(std::shr(highbit, 5) == (highbit_u >> 5));
+ assert(std::shr(highbit, 6) == (highbit_u >> 6));
+ assert(std::shr(highbit, 7) == (highbit_u >> 7));
+ assert(std::shr(highbit, width - 1) == T(1));
+
+ // Overlong right shifts return 0
+ 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) == T(2));
+ assert(std::shr(T(1), -2) == T(4));
+ assert(std::shr(T(1), -3) == T(8));
+ assert(std::shr(T(1), -4) == T(16));
+ assert(std::shr(T(1), -5) == T(32));
+ assert(std::shr(T(1), -6) == T(64));
+ assert(std::shr(T(1), -7) == T(128));
+ assert(std::shr(T(1), -(width - 1)) == (T(1) << (width - 1)));
+
+ // Negative overlong shifts always return 0
+ 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));
+
+ if constexpr (width == 128) {
+ assert(std::shr(highbit, 64) == (highbit_u >> 64));
+ assert(std::shr(highbit, 127) == T(1));
+ assert(std::shr(highbit, 128) == T(0));
+ assert(std::shr(highbit, 200) == T(0));
+ 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));
+ } else if constexpr (width == 256) {
+ assert(std::shr(highbit, 64) == (highbit_u >> 64));
+ assert(std::shr(highbit, 127) == (highbit_u >> 127));
+ assert(std::shr(highbit, 128) == (highbit_u >> 128));
+ assert(std::shr(highbit, 200) == (highbit_u >> 200));
+ assert(std::shr(highbit, 255) == T(1));
+ assert(std::shr(highbit, 256) == T(0));
+ assert(std::shr(highbit, 300) == T(0));
+ 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(1) << 128);
+ assert(std::shr(T(1), -200) == T(1) << 200);
+ assert(std::shr(T(1), -255) == T(1) << 255);
+ assert(std::shr(T(1), -256) == T(0));
+ assert(std::shr(T(1), -300) == T(0));
+ }
+ }
+
+ return true;
+}
+
+struct A {};
+enum E1 : unsigned char { rEd };
+enum class E2 : unsigned char { red };
+
+template <class T>
+concept can_shr_value = requires(T value) { std::shr(value, 1); };
+
+template <class T>
+concept can_shr_shift = requires(T shift) { std::shr(1, shift); };
+
+int main(int, char**) {
+ static_assert(!can_shr_value<bool>);
+ static_assert(!can_shr_value<char>);
+ static_assert(!can_shr_value<wchar_t>);
+ static_assert(!can_shr_value<char8_t>);
+ static_assert(!can_shr_value<char16_t>);
+ static_assert(!can_shr_value<char32_t>);
+ static_assert(!can_shr_value<A>);
+ static_assert(!can_shr_value<A*>);
+ static_assert(!can_shr_value<E1>);
+ static_assert(!can_shr_value<E2>);
+
+ static_assert(!can_shr_shift<bool>);
+ static_assert(!can_shr_shift<char>);
+ static_assert(!can_shr_shift<wchar_t>);
+ static_assert(!can_shr_shift<char8_t>);
+ static_assert(!can_shr_shift<char16_t>);
+ static_assert(!can_shr_shift<char32_t>);
+ static_assert(!can_shr_shift<A>);
+ static_assert(!can_shr_shift<A*>);
+ static_assert(!can_shr_shift<E1>);
+ static_assert(!can_shr_shift<E2>);
+
+ constexpr auto test_type = []<class T> {
+ static_assert(test<T>());
+ test<T>();
+ };
+
+ types::for_each(types::unsigned_integer_types{}, test_type);
+ types::for_each(types::signed_integer_types{}, test_type);
+
+#if TEST_HAS_BITINT
+ using bitint_types = types::type_list<
+ signed _BitInt(32),
+ unsigned _BitInt(32),
+ signed _BitInt(64),
+ unsigned _BitInt(64)
+# if __BITINT_MAXWIDTH__ >= 128
+ ,
+ signed _BitInt(128),
+ unsigned _BitInt(128)
+# endif
+# if __BITINT_MAXWIDTH__ >= 256
+ ,
+ signed _BitInt(256),
+ unsigned _BitInt(256)
+# endif
+ >;
+ types::for_each(bitint_types{}, test_type);
+#endif
+
+ 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