[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:04:24 PDT 2026


================
@@ -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)
----------------
eisenwave wrote:

In any case, the new version is just treating every type the same using `types::for_each`.

Maybe `rotl` and `rotr` tests could be modernized eventually.

https://github.com/llvm/llvm-project/pull/219389


More information about the libcxx-commits mailing list