From e8bc5baf2e9097de479d5a99f532af8763fdf4bd Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 24 Apr 2024 20:53:47 +0300 Subject: [PATCH 01/12] optimize `std::iota` --- benchmarks/CMakeLists.txt | 1 + benchmarks/src/iota.cpp | 27 +++++++++++++++++++++++++++ stl/inc/numeric | 30 ++++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+) create mode 100644 benchmarks/src/iota.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index 1c05a7f7638..0aaca52f99e 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -111,6 +111,7 @@ endfunction() add_benchmark(bitset_to_string src/bitset_to_string.cpp) add_benchmark(find_and_count src/find_and_count.cpp) add_benchmark(find_first_of src/find_first_of.cpp) +add_benchmark(iota src/iota.cpp) add_benchmark(locale_classic src/locale_classic.cpp) add_benchmark(minmax_element src/minmax_element.cpp) add_benchmark(mismatch src/mismatch.cpp) diff --git a/benchmarks/src/iota.cpp b/benchmarks/src/iota.cpp new file mode 100644 index 00000000000..7e1d9f88792 --- /dev/null +++ b/benchmarks/src/iota.cpp @@ -0,0 +1,27 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +#include +#include + +template +void bm(benchmark::State& state) { + const size_t size = static_cast(state.range(0)); + + std::vector a(size); + + for (auto _ : state) { + std::iota(a.begin(), a.end(), T{22}); + benchmark::DoNotOptimize(a); + } +} + +void common_args(auto bm) { + bm->Arg(7)->Arg(18)->Arg(43)->Arg(131)->Arg(315)->Arg(1212); +} + +BENCHMARK(bm)->Apply(common_args); +BENCHMARK(bm)->Apply(common_args); + +BENCHMARK_MAIN(); diff --git a/stl/inc/numeric b/stl/inc/numeric index 6bad9695f31..35235362b8c 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -511,12 +511,42 @@ _FwdIt2 adjacent_difference(_ExPo&& _Exec, const _FwdIt1 _First, const _FwdIt1 _ } #endif // _HAS_CXX17 +template +constexpr bool _Iota_optimization_can_overflow(const size_t _Size) { + if constexpr (is_unsigned_v<_Ty>) { + // _Ty overflow is defined, _Size might not fit _Ty + if constexpr (sizeof(_Ty) >= sizeof(size_t)) { + return false; // any size fits type + } else { + return _Size > (numeric_limits<_Ty>::max)(); + } + } else { + return false; // Signed _Ty overflow is UB, size should fit _Ty range + } +} + _EXPORT_STD template _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { // compute increasing sequence into [_First, _Last) _STD _Adl_verify_range(_First, _Last); auto _UFirst = _STD _Get_unwrapped(_First); const auto _ULast = _STD _Get_unwrapped(_Last); + + if constexpr (_Iterator_is_contiguous && is_integral_v<_Ty> && sizeof(_Ty) >= 4) { + // TRANSITION, DevCom-10593477: help the compiler vectorize + const auto _Ptr = _To_address(_UFirst); + const auto _Size = static_cast(_ULast - _UFirst); + + if (!_Iota_optimization_can_overflow<_Ty>(_Size)) { + const auto _Size_typed = static_cast<_Ty>(_Size); + for (_Ty _Ix = 0; _Ix != _Size_typed; ++_Ix) { + _Ptr[_Ix] = _Val + _Ix; + } + + return; + } + } + for (; _UFirst != _ULast; ++_UFirst, (void) ++_Val) { *_UFirst = _Val; } From d7c34817d1b9012471963c7448ae38203e616f85 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Wed, 24 Apr 2024 22:51:10 +0300 Subject: [PATCH 02/12] no numeric limits! --- stl/inc/numeric | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/numeric b/stl/inc/numeric index 35235362b8c..0a9c34e44b9 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -518,7 +518,7 @@ constexpr bool _Iota_optimization_can_overflow(const size_t _Size) { if constexpr (sizeof(_Ty) >= sizeof(size_t)) { return false; // any size fits type } else { - return _Size > (numeric_limits<_Ty>::max)(); + return _Size > ~_Ty{0}; } } else { return false; // Signed _Ty overflow is UB, size should fit _Ty range From 5d963b2ea54aa08002611423ccc6ddd8f67d0d8b Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 25 Apr 2024 11:40:36 -0700 Subject: [PATCH 03/12] Include more headers. --- benchmarks/src/iota.cpp | 2 ++ 1 file changed, 2 insertions(+) diff --git a/benchmarks/src/iota.cpp b/benchmarks/src/iota.cpp index 7e1d9f88792..cc07ba8701b 100644 --- a/benchmarks/src/iota.cpp +++ b/benchmarks/src/iota.cpp @@ -2,6 +2,8 @@ // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception #include +#include +#include #include #include From ece7526f9d2674c57cb5ecf7102a1f6729029c26 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 25 Apr 2024 11:40:51 -0700 Subject: [PATCH 04/12] Qualify `std::size_t`, use `auto` to avoid repeating it. --- benchmarks/src/iota.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmarks/src/iota.cpp b/benchmarks/src/iota.cpp index cc07ba8701b..9d96d12d773 100644 --- a/benchmarks/src/iota.cpp +++ b/benchmarks/src/iota.cpp @@ -9,7 +9,7 @@ template void bm(benchmark::State& state) { - const size_t size = static_cast(state.range(0)); + const auto size = static_cast(state.range(0)); std::vector a(size); From 172d0e9ff9cde8d54620f74eb468c93897f97375 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Thu, 25 Apr 2024 11:41:22 -0700 Subject: [PATCH 05/12] Flip compile-time test to mirror run-time test. --- stl/inc/numeric | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/stl/inc/numeric b/stl/inc/numeric index 0a9c34e44b9..4399a9bfde4 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -515,10 +515,10 @@ template constexpr bool _Iota_optimization_can_overflow(const size_t _Size) { if constexpr (is_unsigned_v<_Ty>) { // _Ty overflow is defined, _Size might not fit _Ty - if constexpr (sizeof(_Ty) >= sizeof(size_t)) { - return false; // any size fits type - } else { + if constexpr (sizeof(size_t) > sizeof(_Ty)) { return _Size > ~_Ty{0}; + } else { + return false; // any size fits type } } else { return false; // Signed _Ty overflow is UB, size should fit _Ty range From 16b03637d9f0464f21debf88499fb5c5eef5f0d7 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 26 Apr 2024 08:27:13 +0300 Subject: [PATCH 06/12] ADL safety for consistency --- stl/inc/numeric | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stl/inc/numeric b/stl/inc/numeric index 4399a9bfde4..53651c159f2 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -537,7 +537,7 @@ _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { const auto _Ptr = _To_address(_UFirst); const auto _Size = static_cast(_ULast - _UFirst); - if (!_Iota_optimization_can_overflow<_Ty>(_Size)) { + if (!_STD _Iota_optimization_can_overflow<_Ty>(_Size)) { const auto _Size_typed = static_cast<_Ty>(_Size); for (_Ty _Ix = 0; _Ix != _Size_typed; ++_Ix) { _Ptr[_Ix] = _Val + _Ix; From ad54d4f7fcd86f63c3732f6fd58a6e09a45f533d Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Fri, 26 Apr 2024 08:33:42 +0300 Subject: [PATCH 07/12] test range of signed too --- stl/inc/numeric | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/stl/inc/numeric b/stl/inc/numeric index 53651c159f2..e0386e7ae61 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -514,14 +514,19 @@ _FwdIt2 adjacent_difference(_ExPo&& _Exec, const _FwdIt1 _First, const _FwdIt1 _ template constexpr bool _Iota_optimization_can_overflow(const size_t _Size) { if constexpr (is_unsigned_v<_Ty>) { - // _Ty overflow is defined, _Size might not fit _Ty if constexpr (sizeof(size_t) > sizeof(_Ty)) { - return _Size > ~_Ty{0}; + return _Size > ~_Ty{0}; // _Size might not fit _Ty } else { return false; // any size fits type } } else { - return false; // Signed _Ty overflow is UB, size should fit _Ty range + // _Ty overflow is undefined, but it may start from a negative value, + // so _Size may oveflow when _Ty still doesn't + if constexpr (sizeof(size_t) >= sizeof(_Ty)) { + return _Size > ~make_unsigned_t<_Ty>{0} >> 1; + } else { + return false; // any size fits type + } } } From b399a0aad637f9f375fd72f80b40bd1d3dfb8d94 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Apr 2024 00:57:56 -0700 Subject: [PATCH 08/12] `in_range` extraction, part 1: Internal `_Min_limit`/`_Max_limit` don't need to be C++20 `consteval`. They're always stored in `constexpr` variables. --- stl/inc/utility | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/utility b/stl/inc/utility index cd224102e59..4cde9c8d94e 100644 --- a/stl/inc/utility +++ b/stl/inc/utility @@ -847,7 +847,7 @@ _NODISCARD constexpr bool cmp_greater_equal(const _Ty1 _Left, const _Ty2 _Right) } template -_NODISCARD consteval _Ty _Min_limit() noexcept { // same as (numeric_limits<_Ty>::min)(), less throughput cost +_NODISCARD constexpr _Ty _Min_limit() noexcept { // same as (numeric_limits<_Ty>::min)(), less throughput cost static_assert(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types if constexpr (is_signed_v<_Ty>) { constexpr auto _Unsigned_max = static_cast>(-1); @@ -858,7 +858,7 @@ _NODISCARD consteval _Ty _Min_limit() noexcept { // same as (numeric_limits<_Ty> } template -_NODISCARD consteval _Ty _Max_limit() noexcept { // same as (numeric_limits<_Ty>::max)(), less throughput cost +_NODISCARD constexpr _Ty _Max_limit() noexcept { // same as (numeric_limits<_Ty>::max)(), less throughput cost static_assert(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types if constexpr (is_signed_v<_Ty>) { constexpr auto _Unsigned_max = static_cast>(-1); From 8c315b49f5a764f68c60434ddbdb0049bc75f66c Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Apr 2024 01:12:46 -0700 Subject: [PATCH 09/12] `in_range` extraction, part 2: Unconditionally provide `_Ugly` functions. The `_Ugly` functions aren't marked `_EXPORT_STD`. --- stl/inc/utility | 77 +++++++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 21 deletions(-) diff --git a/stl/inc/utility b/stl/inc/utility index 4cde9c8d94e..714f44c5d58 100644 --- a/stl/inc/utility +++ b/stl/inc/utility @@ -788,7 +788,6 @@ _EXPORT_STD template constexpr in_place_index_t<_Idx> in_place_index{}; #endif // _HAS_CXX17 -#if _HAS_CXX20 template constexpr bool _Is_standard_integer = is_integral_v<_Ty> && !_Is_any_of_v, bool, char, @@ -800,8 +799,8 @@ constexpr bool _Is_standard_integer = is_integral_v<_Ty> #endif // defined(__cpp_char8_t) char16_t, char32_t>; -_EXPORT_STD template -_NODISCARD constexpr bool cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { +template +_NODISCARD constexpr bool _Cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { static_assert(_Is_standard_integer<_Ty1> && _Is_standard_integer<_Ty2>, "The integer comparison functions only accept standard and extended integer types."); if constexpr (is_signed_v<_Ty1> == is_signed_v<_Ty2>) { @@ -813,13 +812,13 @@ _NODISCARD constexpr bool cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcep } } -_EXPORT_STD template -_NODISCARD constexpr bool cmp_not_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { - return !_STD cmp_equal(_Left, _Right); +template +_NODISCARD constexpr bool _Cmp_not_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return !_STD _Cmp_equal(_Left, _Right); } -_EXPORT_STD template -_NODISCARD constexpr bool cmp_less(const _Ty1 _Left, const _Ty2 _Right) noexcept { +template +_NODISCARD constexpr bool _Cmp_less(const _Ty1 _Left, const _Ty2 _Right) noexcept { static_assert(_Is_standard_integer<_Ty1> && _Is_standard_integer<_Ty2>, "The integer comparison functions only accept standard and extended integer types."); if constexpr (is_signed_v<_Ty1> == is_signed_v<_Ty2>) { @@ -831,19 +830,19 @@ _NODISCARD constexpr bool cmp_less(const _Ty1 _Left, const _Ty2 _Right) noexcept } } -_EXPORT_STD template -_NODISCARD constexpr bool cmp_greater(const _Ty1 _Left, const _Ty2 _Right) noexcept { - return _STD cmp_less(_Right, _Left); +template +_NODISCARD constexpr bool _Cmp_greater(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_less(_Right, _Left); } -_EXPORT_STD template -_NODISCARD constexpr bool cmp_less_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { - return !_STD cmp_less(_Right, _Left); +template +_NODISCARD constexpr bool _Cmp_less_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return !_STD _Cmp_less(_Right, _Left); } -_EXPORT_STD template -_NODISCARD constexpr bool cmp_greater_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { - return !_STD cmp_less(_Left, _Right); +template +_NODISCARD constexpr bool _Cmp_greater_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return !_STD _Cmp_less(_Left, _Right); } template @@ -868,15 +867,15 @@ _NODISCARD constexpr _Ty _Max_limit() noexcept { // same as (numeric_limits<_Ty> } } -_EXPORT_STD template -_NODISCARD constexpr bool in_range(const _Ty _Value) noexcept { +template +_NODISCARD constexpr bool _In_range(const _Ty _Value) noexcept { static_assert(_Is_standard_integer<_Rx> && _Is_standard_integer<_Ty>, "The integer comparison functions only accept standard and extended integer types."); constexpr auto _Ty_min = _Min_limit<_Ty>(); constexpr auto _Rx_min = _Min_limit<_Rx>(); - if constexpr (_STD cmp_less(_Ty_min, _Rx_min)) { + if constexpr (_STD _Cmp_less(_Ty_min, _Rx_min)) { if (_Value < _Ty{_Rx_min}) { return false; } @@ -885,7 +884,7 @@ _NODISCARD constexpr bool in_range(const _Ty _Value) noexcept { constexpr auto _Ty_max = _Max_limit<_Ty>(); constexpr auto _Rx_max = _Max_limit<_Rx>(); - if constexpr (_STD cmp_greater(_Ty_max, _Rx_max)) { + if constexpr (_STD _Cmp_greater(_Ty_max, _Rx_max)) { if (_Value > _Ty{_Rx_max}) { return false; } @@ -893,6 +892,42 @@ _NODISCARD constexpr bool in_range(const _Ty _Value) noexcept { return true; } + +#if _HAS_CXX20 +_EXPORT_STD template +_NODISCARD constexpr bool cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_equal(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool cmp_not_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_not_equal(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool cmp_less(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_less(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool cmp_greater(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_greater(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool cmp_less_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_less_equal(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool cmp_greater_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { + return _STD _Cmp_greater_equal(_Left, _Right); +} + +_EXPORT_STD template +_NODISCARD constexpr bool in_range(const _Ty _Value) noexcept { + return _STD _In_range<_Rx>(_Value); +} #endif // _HAS_CXX20 #if _HAS_CXX23 From 0b258ce28e1e93f7bb9fc43c9061828353b6d860 Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Apr 2024 01:19:45 -0700 Subject: [PATCH 10/12] Cleanup: Simplify `_Is_standard_integer`. This matches how we implement `_Is_standard_unsigned_integer` in `<__msvc_bit_utils.hpp>`. --- stl/inc/utility | 11 ++--------- 1 file changed, 2 insertions(+), 9 deletions(-) diff --git a/stl/inc/utility b/stl/inc/utility index 714f44c5d58..ad262f0d91f 100644 --- a/stl/inc/utility +++ b/stl/inc/utility @@ -789,15 +789,8 @@ constexpr in_place_index_t<_Idx> in_place_index{}; #endif // _HAS_CXX17 template -constexpr bool _Is_standard_integer = is_integral_v<_Ty> - && !_Is_any_of_v, bool, char, -#ifdef _NATIVE_WCHAR_T_DEFINED - wchar_t, -#endif // _NATIVE_WCHAR_T_DEFINED -#ifdef __cpp_char8_t - char8_t, -#endif // defined(__cpp_char8_t) - char16_t, char32_t>; +constexpr bool _Is_standard_integer = _Is_any_of_v, signed char, short, int, long, long long, + unsigned char, unsigned short, unsigned int, unsigned long, unsigned long long>; template _NODISCARD constexpr bool _Cmp_equal(const _Ty1 _Left, const _Ty2 _Right) noexcept { From 23d696abeb5cd359fbcc5e621d8936bf9769ad5e Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Apr 2024 01:25:48 -0700 Subject: [PATCH 11/12] Simplify optimization with `_In_range<_Ty>(_Size)`. This answers the question of whether `static_cast<_Ty>(_Size)` is value-preserving. --- stl/inc/numeric | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/stl/inc/numeric b/stl/inc/numeric index e0386e7ae61..ccd04b65752 100644 --- a/stl/inc/numeric +++ b/stl/inc/numeric @@ -511,25 +511,6 @@ _FwdIt2 adjacent_difference(_ExPo&& _Exec, const _FwdIt1 _First, const _FwdIt1 _ } #endif // _HAS_CXX17 -template -constexpr bool _Iota_optimization_can_overflow(const size_t _Size) { - if constexpr (is_unsigned_v<_Ty>) { - if constexpr (sizeof(size_t) > sizeof(_Ty)) { - return _Size > ~_Ty{0}; // _Size might not fit _Ty - } else { - return false; // any size fits type - } - } else { - // _Ty overflow is undefined, but it may start from a negative value, - // so _Size may oveflow when _Ty still doesn't - if constexpr (sizeof(size_t) >= sizeof(_Ty)) { - return _Size > ~make_unsigned_t<_Ty>{0} >> 1; - } else { - return false; // any size fits type - } - } -} - _EXPORT_STD template _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { // compute increasing sequence into [_First, _Last) @@ -542,7 +523,7 @@ _CONSTEXPR20 void iota(_FwdIt _First, _FwdIt _Last, _Ty _Val) { const auto _Ptr = _To_address(_UFirst); const auto _Size = static_cast(_ULast - _UFirst); - if (!_STD _Iota_optimization_can_overflow<_Ty>(_Size)) { + if (_STD _In_range<_Ty>(_Size)) { const auto _Size_typed = static_cast<_Ty>(_Size); for (_Ty _Ix = 0; _Ix != _Size_typed; ++_Ix) { _Ptr[_Ix] = _Val + _Ix; From 12325cce6607ef5409b6b708304ddd2a13dd58de Mon Sep 17 00:00:00 2001 From: "Stephan T. Lavavej" Date: Fri, 26 Apr 2024 02:09:30 -0700 Subject: [PATCH 12/12] Use `_STL_INTERNAL_STATIC_ASSERT` instead of C++17 terse `static_assert` in `_Min_limit`/`_Max_limit`. These are internal helpers, and the "public" `_In_range` validates the user-provided types. --- stl/inc/utility | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stl/inc/utility b/stl/inc/utility index ad262f0d91f..070899fb3fa 100644 --- a/stl/inc/utility +++ b/stl/inc/utility @@ -840,7 +840,7 @@ _NODISCARD constexpr bool _Cmp_greater_equal(const _Ty1 _Left, const _Ty2 _Right template _NODISCARD constexpr _Ty _Min_limit() noexcept { // same as (numeric_limits<_Ty>::min)(), less throughput cost - static_assert(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types + _STL_INTERNAL_STATIC_ASSERT(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types if constexpr (is_signed_v<_Ty>) { constexpr auto _Unsigned_max = static_cast>(-1); return static_cast<_Ty>((_Unsigned_max >> 1) + 1); // well-defined, N4950 [conv.integral]/3 @@ -851,7 +851,7 @@ _NODISCARD constexpr _Ty _Min_limit() noexcept { // same as (numeric_limits<_Ty> template _NODISCARD constexpr _Ty _Max_limit() noexcept { // same as (numeric_limits<_Ty>::max)(), less throughput cost - static_assert(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types + _STL_INTERNAL_STATIC_ASSERT(_Is_standard_integer<_Ty>); // doesn't attempt to handle all types if constexpr (is_signed_v<_Ty>) { constexpr auto _Unsigned_max = static_cast>(-1); return static_cast<_Ty>(_Unsigned_max >> 1);