-
Notifications
You must be signed in to change notification settings - Fork 14.3k
[libc++] Fix {std, ranges}::copy for vector<bool> with small storage types #131545
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
b316676
to
777b9d2
Compare
@llvm/pr-subscribers-libcxx Author: Peng Liu (winner245) ChangesThe current implementation of This patch resolves the issue by correcting the internal bitwise operations, ensuring that Fixes #131692. Patch is 22.69 KiB, truncated to 20.00 KiB below, full version: https://github.com/llvm/llvm-project/pull/131545.diff 5 Files Affected:
diff --git a/libcxx/include/__algorithm/copy.h b/libcxx/include/__algorithm/copy.h
index 7454c874a4d93..ea98031df11ad 100644
--- a/libcxx/include/__algorithm/copy.h
+++ b/libcxx/include/__algorithm/copy.h
@@ -53,7 +53,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
unsigned __clz = __bits_per_word - __first.__ctz_;
difference_type __dn = std::min(static_cast<difference_type>(__clz), __n);
__n -= __dn;
- __storage_type __m = (~__storage_type(0) << __first.__ctz_) & (~__storage_type(0) >> (__clz - __dn));
+ __storage_type __m = std::__middle_mask<__storage_type>(__clz - __dn, __first.__ctz_);
__storage_type __b = *__first.__seg_ & __m;
*__result.__seg_ &= ~__m;
*__result.__seg_ |= __b;
@@ -73,7 +73,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
// do last word
if (__n > 0) {
__first.__seg_ += __nw;
- __storage_type __m = ~__storage_type(0) >> (__bits_per_word - __n);
+ __storage_type __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n);
__storage_type __b = *__first.__seg_ & __m;
*__result.__seg_ &= ~__m;
*__result.__seg_ |= __b;
@@ -98,11 +98,11 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
unsigned __clz_f = __bits_per_word - __first.__ctz_;
difference_type __dn = std::min(static_cast<difference_type>(__clz_f), __n);
__n -= __dn;
- __storage_type __m = (~__storage_type(0) << __first.__ctz_) & (~__storage_type(0) >> (__clz_f - __dn));
+ __storage_type __m = std::__middle_mask<__storage_type>(__clz_f - __dn, __first.__ctz_);
__storage_type __b = *__first.__seg_ & __m;
unsigned __clz_r = __bits_per_word - __result.__ctz_;
__storage_type __ddn = std::min<__storage_type>(__dn, __clz_r);
- __m = (~__storage_type(0) << __result.__ctz_) & (~__storage_type(0) >> (__clz_r - __ddn));
+ __m = std::__middle_mask<__storage_type>(__clz_r - __ddn, __result.__ctz_);
*__result.__seg_ &= ~__m;
if (__result.__ctz_ > __first.__ctz_)
*__result.__seg_ |= __b << (__result.__ctz_ - __first.__ctz_);
@@ -112,7 +112,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
__result.__ctz_ = static_cast<unsigned>((__ddn + __result.__ctz_) % __bits_per_word);
__dn -= __ddn;
if (__dn > 0) {
- __m = ~__storage_type(0) >> (__bits_per_word - __dn);
+ __m = std::__trailing_mask<__storage_type>(__bits_per_word - __dn);
*__result.__seg_ &= ~__m;
*__result.__seg_ |= __b >> (__first.__ctz_ + __ddn);
__result.__ctz_ = static_cast<unsigned>(__dn);
@@ -123,7 +123,7 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
// __first.__ctz_ == 0;
// do middle words
unsigned __clz_r = __bits_per_word - __result.__ctz_;
- __storage_type __m = ~__storage_type(0) << __result.__ctz_;
+ __storage_type __m = std::__leading_mask<__storage_type>(__result.__ctz_);
for (; __n >= __bits_per_word; __n -= __bits_per_word, ++__first.__seg_) {
__storage_type __b = *__first.__seg_;
*__result.__seg_ &= ~__m;
@@ -134,17 +134,17 @@ _LIBCPP_CONSTEXPR_SINCE_CXX20 _LIBCPP_HIDE_FROM_ABI __bit_iterator<_Cp, false> _
}
// do last word
if (__n > 0) {
- __m = ~__storage_type(0) >> (__bits_per_word - __n);
+ __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n);
__storage_type __b = *__first.__seg_ & __m;
__storage_type __dn = std::min(__n, static_cast<difference_type>(__clz_r));
- __m = (~__storage_type(0) << __result.__ctz_) & (~__storage_type(0) >> (__clz_r - __dn));
+ __m = std::__middle_mask<__storage_type>(__clz_r - __dn, __result.__ctz_);
*__result.__seg_ &= ~__m;
*__result.__seg_ |= __b << __result.__ctz_;
__result.__seg_ += (__dn + __result.__ctz_) / __bits_per_word;
__result.__ctz_ = static_cast<unsigned>((__dn + __result.__ctz_) % __bits_per_word);
__n -= __dn;
if (__n > 0) {
- __m = ~__storage_type(0) >> (__bits_per_word - __n);
+ __m = std::__trailing_mask<__storage_type>(__bits_per_word - __n);
*__result.__seg_ &= ~__m;
*__result.__seg_ |= __b >> __dn;
__result.__ctz_ = static_cast<unsigned>(__n);
diff --git a/libcxx/include/__bit_reference b/libcxx/include/__bit_reference
index 58cbd4d51c88e..f5c22fc0a3ade 100644
--- a/libcxx/include/__bit_reference
+++ b/libcxx/include/__bit_reference
@@ -82,13 +82,20 @@ _LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask
return static_cast<_StorageType>(~static_cast<_StorageType>(0)) >> __clz;
}
+// Creates a mask of type `_StorageType` with a specified number of trailing zeros (__ctz) and sets all remaining
+// bits to one.
+template <class _StorageType>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz) {
+ static_assert(is_unsigned<_StorageType>::value, "__leading_mask only works with unsigned types");
+ return static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz;
+}
+
// Creates a mask of type `_StorageType` with a specified number of leading zeros (__clz), a specified number of
// trailing zeros (__ctz), and sets all bits in between to one.
template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz) {
static_assert(is_unsigned<_StorageType>::value, "__middle_mask only works with unsigned types");
- return (static_cast<_StorageType>(~static_cast<_StorageType>(0)) << __ctz) &
- std::__trailing_mask<_StorageType>(__clz);
+ return std::__leading_mask<_StorageType>(__ctz) & std::__trailing_mask<_StorageType>(__clz);
}
// This function is designed to operate correctly even for smaller integral types like `uint8_t`, `uint16_t`,
diff --git a/libcxx/include/__fwd/bit_reference.h b/libcxx/include/__fwd/bit_reference.h
index e212667f3de1f..36058d59cc22a 100644
--- a/libcxx/include/__fwd/bit_reference.h
+++ b/libcxx/include/__fwd/bit_reference.h
@@ -28,11 +28,14 @@ struct __size_difference_type_traits;
template <class _StoragePointer>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX14 void
-__fill_masked_range(_StoragePointer __word, unsigned __ctz, unsigned __clz, bool __fill_val);
+__fill_masked_range(_StoragePointer __word, unsigned __clz, unsigned __ctz, bool __fill_val);
template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __trailing_mask(unsigned __clz);
+template <class _StorageType>
+_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __leading_mask(unsigned __ctz);
+
template <class _StorageType>
_LIBCPP_HIDE_FROM_ABI _LIBCPP_CONSTEXPR_SINCE_CXX20 _StorageType __middle_mask(unsigned __clz, unsigned __ctz);
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp
index 3d4ee23a5a7ff..cfcaf1c8a6dd7 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/copy.pass.cpp
@@ -12,10 +12,13 @@
// constexpr OutIter // constexpr after C++17
// copy(InIter first, InIter last, OutIter result);
+// XFAIL: FROZEN-CXX03-HEADERS-FIXME
+
#include <algorithm>
#include <cassert>
#include <vector>
+#include "sized_allocator.h"
#include "test_macros.h"
#include "test_iterators.h"
#include "type_algorithms.h"
@@ -109,6 +112,153 @@ TEST_CONSTEXPR_CXX20 bool test() {
assert(test_vector_bool(256));
}
+ // Validate std::copy with std::vector<bool> iterators and custom storage types.
+ // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged.
+ // Related issue: https://github.com/llvm/llvm-project/issues/131692.
+ {
+ //// Tests for std::copy with aligned bits
+
+ { // Test the first (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::copy(in.begin() + 1, in.begin() + 2, out.begin() + 1); // out[1] = false
+ assert(out[1] == false);
+ for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 1)
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::copy(in.begin(), in.begin() + 1, out.begin()); // out[0] = false
+ assert(out[0] == false);
+ for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(32, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(32, false, Alloc(1));
+ std::copy(in.begin() + 4, in.end() - 4, out.begin() + 4);
+ for (std::size_t i = 4; i < static_cast<std::size_t>(in.size() - 4); ++i)
+ assert(in[i] == out[i]);
+ for (std::size_t i = 0; i < 4; ++i)
+ assert(out[i] == false);
+ for (std::size_t i = 28; i < out.size(); ++i)
+ assert(out[i] == false);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::copy(in.begin() + 1, in.begin() + 3, out.begin() + 1); // out[1..2] = false
+ assert(out[1] == false);
+ assert(out[2] == false);
+ for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 1 && i != 2)
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::copy(in.begin(), in.begin() + 2, out.begin()); // out[0..1] = false
+ assert(out[0] == false);
+ assert(out[1] == false);
+ for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(64, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(64, false, Alloc(1));
+ std::copy(in.begin() + 8, in.end() - 8, out.begin() + 8);
+ for (std::size_t i = 8; i < static_cast<std::size_t>(in.size() - 8); ++i)
+ assert(in[i] == out[i]);
+ for (std::size_t i = 0; i < 8; ++i)
+ assert(out[i] == false);
+ for (std::size_t i = static_cast<std::size_t>(out.size() - 8); i < out.size(); ++i)
+ assert(out[i] == false);
+ }
+
+ //// Tests for std::copy with unaligned bits
+
+ { // Test the first (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::copy(in.begin() + 7, in.end(), out.begin()); // out[0] = false
+ assert(out[0] == false);
+ for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::copy(in.begin(), in.begin() + 1, out.begin() + 2); // out[2] = false
+ assert(out[2] == false);
+ for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 2)
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(36, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(40, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 4);
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ for (std::size_t i = 0; i < 4; ++i)
+ assert(out[i] == false);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::copy(in.begin() + 14, in.end(), out.begin()); // out[0..1] = false
+ assert(out[0] == false);
+ assert(out[1] == false);
+ for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::copy(in.begin(), in.begin() + 2, out.begin() + 1); // out[1..2] = false
+ assert(out[1] == false);
+ assert(out[2] == false);
+ for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 1 && i != 2)
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(72, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(80, false, Alloc(1));
+ std::copy(in.begin(), in.end(), out.begin() + 4);
+ for (std::size_t i = 0; i < in.size(); ++i)
+ assert(in[i] == out[i + 4]);
+ for (std::size_t i = 0; i < 4; ++i)
+ assert(out[i] == false);
+ for (std::size_t i = in.size() + 4; i < out.size(); ++i)
+ assert(out[i] == false);
+ }
+ }
+
return true;
}
diff --git a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp
index 68356c80ba7f6..f541c914b401b 100644
--- a/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp
+++ b/libcxx/test/std/algorithms/alg.modifying.operations/alg.copy/ranges.copy.pass.cpp
@@ -25,6 +25,7 @@
#include <vector>
#include "almost_satisfies_types.h"
+#include "sized_allocator.h"
#include "test_iterators.h"
#include "test_macros.h"
#include "type_algorithms.h"
@@ -237,6 +238,153 @@ constexpr bool test() {
assert(test_vector_bool(199));
assert(test_vector_bool(256));
}
+
+ // Validate std::ranges::copy with std::vector<bool> iterators and custom storage types.
+ // Ensure that assigned bits hold the intended values, while unassigned bits stay unchanged.
+ // Related issue: https://github.com/llvm/llvm-project/issues/131692.
+ {
+ //// Tests for std::ranges::copy with aligned bits
+
+ { // Test the first (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 1, in.begin() + 2), out.begin() + 1); // out[1] = false
+ assert(out[1] == false);
+ for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 1)
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin(), in.begin() + 1), out.begin()); // out[0] = false
+ assert(out[0] == false);
+ for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(32, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(32, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 4, in.end() - 4), out.begin() + 4);
+ for (std::size_t i = 4; i < static_cast<std::size_t>(in.size() - 4); ++i)
+ assert(in[i] == out[i]);
+ for (std::size_t i = 0; i < 4; ++i)
+ assert(out[i] == false);
+ for (std::size_t i = 28; i < out.size(); ++i)
+ assert(out[i] == false);
+ }
+
+ { // Test the first (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 1, in.begin() + 3), out.begin() + 1); // out[1..2] = false
+ assert(out[1] == false);
+ assert(out[2] == false);
+ for (std::size_t i = 0; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ if (i != 1 && i != 2)
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(16, false, Alloc(1));
+ std::vector<bool, Alloc> out(16, true, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin(), in.begin() + 2), out.begin()); // out[0..1] = false
+ assert(out[0] == false);
+ assert(out[1] == false);
+ for (std::size_t i = 2; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test middle (whole) words for uint16_t
+ using Alloc = sized_allocator<bool, std::uint16_t, std::int16_t>;
+ std::vector<bool, Alloc> in(64, true, Alloc(1));
+ for (std::size_t i = 0; i < in.size(); i += 2)
+ in[i] = false;
+ std::vector<bool, Alloc> out(64, false, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 8, in.end() - 8), out.begin() + 8);
+ for (std::size_t i = 8; i < static_cast<std::size_t>(in.size() - 8); ++i)
+ assert(in[i] == out[i]);
+ for (std::size_t i = 0; i < 8; ++i)
+ assert(out[i] == false);
+ for (std::size_t i = static_cast<std::size_t>(out.size() - 8); i < out.size(); ++i)
+ assert(out[i] == false);
+ }
+
+ //// Tests for std::ranges::copy with unaligned bits
+
+ { // Test the first (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, Alloc> out(8, true, Alloc(1));
+ std::ranges::copy(std::ranges::subrange(in.begin() + 7, in.end()), out.begin()); // out[0] = false
+ assert(out[0] == false);
+ for (std::size_t i = 1; i < out.size(); ++i) // Ensure that unassigned bits remain unchanged
+ assert(out[i] == true);
+ }
+ { // Test the last (partial) word for uint8_t
+ using Alloc = sized_allocator<bool, std::uint8_t, std::int8_t>;
+ std::vector<bool, Alloc> in(8, false, Alloc(1));
+ std::vector<bool, All...
[truncated]
|
The current implementation of
{std, ranges}::copy
fails to copyvector<bool>
correctly when the underlying storage type (__storage_type
) is smaller thanint
, such asunsigned char
,unsigned short
,uint8_t
anduint16_t
. The root cause is that the unsigned small storage type undergoes integer promotion to (signed)int
, which is then left and right shifted, leading to UB (before C++20) and sign-bit extension (since C++20) respectively. As a result, the underlying bit mask evaluations become incorrect, causing erroneous copying behavior.This patch resolves the issue by correcting the internal bitwise operations, ensuring that
{std, ranges}::copy
operates correctly forvector<bool>
with any custom (unsigned) storage types.Fixes #131692.