[libcxx-commits] [libcxx] [libc++] Fix copy for vector<bool> with small storage types (PR #131545)

Peng Liu via libcxx-commits libcxx-commits at lists.llvm.org
Mon Mar 17 10:53:09 PDT 2025


https://github.com/winner245 updated https://github.com/llvm/llvm-project/pull/131545

>From b316676973e86374deb568ca0612a664798ab8a6 Mon Sep 17 00:00:00 2001
From: Peng Liu <winner245 at hotmail.com>
Date: Sun, 16 Mar 2025 16:46:10 -0400
Subject: [PATCH] Fix copy for vector<bool> with small storage types

---
 libcxx/include/__algorithm/copy.h             |  18 +--
 libcxx/include/__bit_reference                |  11 +-
 libcxx/include/__fwd/bit_reference.h          |   5 +-
 .../alg.copy/copy.pass.cpp                    | 147 ++++++++++++++++++
 .../alg.copy/ranges.copy.pass.cpp             | 147 ++++++++++++++++++
 5 files changed, 316 insertions(+), 12 deletions(-)

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..05fc891a66af6 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
@@ -16,6 +16,7 @@
 #include <cassert>
 #include <vector>
 
+#include "sized_allocator.h"
 #include "test_macros.h"
 #include "test_iterators.h"
 #include "type_algorithms.h"
@@ -109,6 +110,152 @@ 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.
+  {
+    //// 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..49cb4716a7601 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,152 @@ 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.
+  {
+    //// 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, Alloc> out(8, true, Alloc(1));
+      std::ranges::copy(std::ranges::subrange(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::ranges::copy(std::ranges::subrange(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::ranges::copy(std::ranges::subrange(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::ranges::copy(std::ranges::subrange(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::ranges::copy(std::ranges::subrange(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);
+    }
+  }
 #endif
 
   return true;



More information about the libcxx-commits mailing list