From 408cd46fb2f43357fdad40c17e173fc638649f0a Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 7 Mar 2026 18:03:16 +0200 Subject: [PATCH 1/9] Test --- .../test.cpp | 26 +++++++++++++++++++ 1 file changed, 26 insertions(+) diff --git a/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp index 727f196504..196d8eaa09 100644 --- a/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp +++ b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp @@ -1518,6 +1518,30 @@ CONSTEXPR20 bool test_copy_part_2() { return true; } +CONSTEXPR20 bool test_is_permutation() { + const vector source = { + false, true, false, false, false, true, false, true, true, false, false, true, false, false, true, true}; + + // Actually a permutation + const vector perm = { + true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true}; + assert(is_permutation(source.begin(), source.end(), perm.begin())); + assert(is_permutation(source.begin(), source.end(), perm.begin(), perm.end())); + + // One extra true value + const vector extra_true = { + true, false, true, false, false, true, false, false, true, true, true, false, true, false, false, true}; + assert(!is_permutation(source.begin(), source.end(), extra_true.begin())); + assert(!is_permutation(source.begin(), source.end(), extra_true.begin(), extra_true.end())); + + // One element longer + const vector longer = { + true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true, false}; + assert(!is_permutation(source.begin(), source.end(), longer.begin(), longer.end())); + + return true; +} + void initialize_randomness(mt19937_64& gen) { constexpr size_t n = mt19937_64::state_size; constexpr size_t w = mt19937_64::word_size; @@ -1710,6 +1734,7 @@ static_assert(test_meow_of()); #if defined(__clang__) || defined(__EDG__) // TRANSITION, VSO-2574489 static_assert(test_copy_part_1()); static_assert(test_copy_part_2()); +static_assert(test_is_permutation()); #endif // ^^^ no workaround ^^^ #endif // _HAS_CXX20 @@ -1721,6 +1746,7 @@ int main() { test_meow_of(); test_copy_part_1(); test_copy_part_2(); + test_is_permutation(); test_huge_vector_bool(); From 90e156c4da97056396cb39ef8800606fb1900c8d Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 7 Mar 2026 18:09:34 +0200 Subject: [PATCH 2/9] Benchmark --- benchmarks/CMakeLists.txt | 1 + benchmarks/src/vector_bool_permute.cpp | 44 ++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) create mode 100644 benchmarks/src/vector_bool_permute.cpp diff --git a/benchmarks/CMakeLists.txt b/benchmarks/CMakeLists.txt index ad9b1f0a43..6b59ee804f 100644 --- a/benchmarks/CMakeLists.txt +++ b/benchmarks/CMakeLists.txt @@ -146,4 +146,5 @@ add_benchmark(vector_bool_copy_n src/vector_bool_copy_n.cpp) add_benchmark(vector_bool_count src/vector_bool_count.cpp) add_benchmark(vector_bool_meow_of src/vector_bool_meow_of.cpp) add_benchmark(vector_bool_move src/vector_bool_move.cpp) +add_benchmark(vector_bool_permute src/vector_bool_permute.cpp) add_benchmark(vector_bool_transform src/vector_bool_transform.cpp) diff --git a/benchmarks/src/vector_bool_permute.cpp b/benchmarks/src/vector_bool_permute.cpp new file mode 100644 index 0000000000..e1182d2b09 --- /dev/null +++ b/benchmarks/src/vector_bool_permute.cpp @@ -0,0 +1,44 @@ +// Copyright (c) Microsoft Corporation. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception + +#include +// +#include +#include +#include +#include + +#include "utility.hpp" + +using namespace std; + +void perm_check_3(benchmark::State& state) { + const auto size = static_cast(state.range(0)); + vector v1 = random_vector(size); + vector v2 = random_vector(size, 1u); + + for (auto _ : state) { + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + bool r = is_permutation(v1.begin(), v1.end(), v2.begin()); + benchmark::DoNotOptimize(r); + } +} + +void perm_check_4(benchmark::State& state) { + const auto size = static_cast(state.range(0)); + vector v1 = random_vector(size); + vector v2 = random_vector(size, 1u); + + for (auto _ : state) { + benchmark::DoNotOptimize(v1); + benchmark::DoNotOptimize(v2); + bool r = is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end()); + benchmark::DoNotOptimize(r); + } +} + +BENCHMARK(perm_check_3)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_check_4)->RangeMultiplier(64)->Range(64, 64 << 10); + +BENCHMARK_MAIN(); From bf9ff31ece5bee76d62fd010d2b98fccf87596bb Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 7 Mar 2026 18:19:18 +0200 Subject: [PATCH 3/9] Mutation! --- stl/inc/algorithm | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index b925caf990..50792c5eda 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1260,6 +1260,16 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw const auto _ULast1 = _STD _Get_unwrapped(_Last1); auto _UFirst2 = _STD _Get_unwrapped_n(_First2, _STD _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); + if constexpr (_Is_vb_iterator && _Is_vb_iterator + && _Is_any_of_v<_Pr, +#if _HAS_CXX20 + _RANGES equal_to, +#endif + equal_to<>, equal_to>) { + return _STD _Count_vbool(_UFirst1, _ULast1, true) + == _STD _Count_vbool(_UFirst2, _UFirst2 + (_ULast1 - _UFirst1), true); + } + for (;; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix if (_UFirst1 == _ULast1) { // everything matched return true; @@ -1297,6 +1307,15 @@ _NODISCARD _CONSTEXPR20 bool is_permutation( return false; } + if constexpr (_Is_vb_iterator && _Is_vb_iterator + && _Is_any_of_v<_Pr, +#if _HAS_CXX20 + _RANGES equal_to, +#endif + equal_to<>, equal_to>) { + return _STD _Count_vbool(_UFirst1, _ULast1, true) == _STD _Count_vbool(_UFirst2, _ULast2, true); + } + for (; _UFirst1 != _ULast1; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix if (!_Pred(*_UFirst1, *_UFirst2)) { // found first inequality, check match counts in suffix From 549a9d3b4889071646f020049895e550a72c5e57 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sat, 7 Mar 2026 19:53:24 +0200 Subject: [PATCH 4/9] Generalize --- stl/inc/algorithm | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index 50792c5eda..b3e8e591ac 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1260,14 +1260,14 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw const auto _ULast1 = _STD _Get_unwrapped(_Last1); auto _UFirst2 = _STD _Get_unwrapped_n(_First2, _STD _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); - if constexpr (_Is_vb_iterator && _Is_vb_iterator + if constexpr (is_same_v<_Iter_value_t, bool> + && is_same_v<_Iter_value_t, bool> && _Is_any_of_v<_Pr, #if _HAS_CXX20 _RANGES equal_to, #endif equal_to<>, equal_to>) { - return _STD _Count_vbool(_UFirst1, _ULast1, true) - == _STD _Count_vbool(_UFirst2, _UFirst2 + (_ULast1 - _UFirst1), true); + return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _UFirst2 + (_ULast1 - _UFirst1), true); } for (;; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix @@ -1307,13 +1307,14 @@ _NODISCARD _CONSTEXPR20 bool is_permutation( return false; } - if constexpr (_Is_vb_iterator && _Is_vb_iterator + if constexpr (is_same_v<_Iter_value_t, bool> + && is_same_v<_Iter_value_t, bool> && _Is_any_of_v<_Pr, #if _HAS_CXX20 _RANGES equal_to, #endif equal_to<>, equal_to>) { - return _STD _Count_vbool(_UFirst1, _ULast1, true) == _STD _Count_vbool(_UFirst2, _ULast2, true); + return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _ULast2, true); } for (; _UFirst1 != _ULast1; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix From 5a0ba816377c67a2b06d88d16f18dd64c2cc7719 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 8 Mar 2026 09:42:40 +0200 Subject: [PATCH 5/9] benchmark plain dynamic array --- benchmarks/src/vector_bool_permute.cpp | 57 ++++++++++++++++++++++---- 1 file changed, 49 insertions(+), 8 deletions(-) diff --git a/benchmarks/src/vector_bool_permute.cpp b/benchmarks/src/vector_bool_permute.cpp index e1182d2b09..73ef9bafa2 100644 --- a/benchmarks/src/vector_bool_permute.cpp +++ b/benchmarks/src/vector_bool_permute.cpp @@ -12,33 +12,74 @@ using namespace std; -void perm_check_3(benchmark::State& state) { +enum class equality { eq, neq }; +enum class args { three, four }; + + +template +void perm_arr_check(benchmark::State& state) { const auto size = static_cast(state.range(0)); vector v1 = random_vector(size); - vector v2 = random_vector(size, 1u); + vector v2; + if constexpr (Eq == equality::eq) { + v2 = v1; + } else { + v2 = random_vector(size, 1u); + } + + auto a1 = make_unique(size); + auto a2 = make_unique(size); + copy(v1.begin(), v1.end(), a1.get()); + copy(v2.begin(), v2.end(), a2.get()); for (auto _ : state) { benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); - bool r = is_permutation(v1.begin(), v1.end(), v2.begin()); + bool r; + if constexpr (Args == args::three) { + r = is_permutation(a1.get(), a1.get() + size, a2.get()); + } else { + r = is_permutation(a1.get(), a1.get() + size, a2.get(), a2.get() + size); + } benchmark::DoNotOptimize(r); } } -void perm_check_4(benchmark::State& state) { + +template +void perm_vbool_check(benchmark::State& state) { const auto size = static_cast(state.range(0)); vector v1 = random_vector(size); - vector v2 = random_vector(size, 1u); + vector v2; + if constexpr (Eq == equality::eq) { + v2 = v1; + } + else + { + v2 = random_vector(size, 1u); + } for (auto _ : state) { benchmark::DoNotOptimize(v1); benchmark::DoNotOptimize(v2); - bool r = is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end()); + bool r; + if constexpr (Args == args::three) { + r = is_permutation(v1.begin(), v1.end(), v2.begin()); + } else { + r = is_permutation(v1.begin(), v1.end(), v2.begin(), v2.end()); + } benchmark::DoNotOptimize(r); } } -BENCHMARK(perm_check_3)->RangeMultiplier(64)->Range(64, 64 << 10); -BENCHMARK(perm_check_4)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_arr_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_arr_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_arr_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_arr_check)->RangeMultiplier(64)->Range(64, 64 << 10); + +BENCHMARK(perm_vbool_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_vbool_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_vbool_check)->RangeMultiplier(64)->Range(64, 64 << 10); +BENCHMARK(perm_vbool_check)->RangeMultiplier(64)->Range(64, 64 << 10); BENCHMARK_MAIN(); From 443e021073fe965b988f3cb259a136aac4a6e9d6 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 8 Mar 2026 10:33:43 +0200 Subject: [PATCH 6/9] Restore mismatch --- stl/inc/algorithm | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index b3e8e591ac..dd32dd766d 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1267,6 +1267,16 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw _RANGES equal_to, #endif equal_to<>, equal_to>) { + if constexpr (!_Is_vb_iterator && !_Is_vb_iterator) { + auto _Pair = _STD mismatch(_UFirst1, _ULast1, _UFirst2); + _UFirst1 = _STD move(_Pair.first); + _UFirst2 = _STD move(_Pair.second); + + if (_UFirst1 == _ULast1) { + return true; + } + } + return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _UFirst2 + (_ULast1 - _UFirst1), true); } @@ -1314,6 +1324,16 @@ _NODISCARD _CONSTEXPR20 bool is_permutation( _RANGES equal_to, #endif equal_to<>, equal_to>) { + if constexpr (!_Is_vb_iterator && !_Is_vb_iterator) { + auto _Pair = _STD mismatch(_UFirst1, _ULast1, _UFirst2); + _UFirst1 = _STD move(_Pair.first); + _UFirst2 = _STD move(_Pair.second); + + if (_UFirst1 == _ULast1) { + return true; + } + } + return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _ULast2, true); } From 08b26573a4607aebd1959be5988e07a80342e89c Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 8 Mar 2026 10:57:24 +0200 Subject: [PATCH 7/9] format --- benchmarks/src/vector_bool_permute.cpp | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/benchmarks/src/vector_bool_permute.cpp b/benchmarks/src/vector_bool_permute.cpp index 73ef9bafa2..a02ef61c5a 100644 --- a/benchmarks/src/vector_bool_permute.cpp +++ b/benchmarks/src/vector_bool_permute.cpp @@ -53,9 +53,7 @@ void perm_vbool_check(benchmark::State& state) { vector v2; if constexpr (Eq == equality::eq) { v2 = v1; - } - else - { + } else { v2 = random_vector(size, 1u); } From 4ca22be012d0f02a92d56fc404d82860bc95b982 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 8 Mar 2026 11:56:15 +0200 Subject: [PATCH 8/9] Admit we rely on random access iterators, extract --- stl/inc/algorithm | 43 ++++++++++++++++++++----------------------- 1 file changed, 20 insertions(+), 23 deletions(-) diff --git a/stl/inc/algorithm b/stl/inc/algorithm index dd32dd766d..2757143f68 100644 --- a/stl/inc/algorithm +++ b/stl/inc/algorithm @@ -1252,6 +1252,21 @@ namespace ranges { } // namespace ranges #endif // _HAS_CXX20 +template +_NODISCARD _CONSTEXPR20 bool _Is_permutation_of_bool(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _FwdIt2 _Last2) { + if constexpr (!_Is_vb_iterator<_FwdIt1> && !_Is_vb_iterator<_FwdIt2>) { + auto _Pair = _STD mismatch(_First1, _Last1, _First2); + _First1 = _STD move(_Pair.first); + _First2 = _STD move(_Pair.second); + + if (_First1 == _Last1) { + return true; + } + } + + return _STD count(_First1, _Last1, true) == _STD count(_First2, _Last2, true); +} + _EXPORT_STD template _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _FwdIt2 _First2, _Pr _Pred) { // test if [_First1, _Last1) == permuted [_First2, ...) @@ -1261,23 +1276,15 @@ _NODISCARD _CONSTEXPR20 bool is_permutation(_FwdIt1 _First1, _FwdIt1 _Last1, _Fw auto _UFirst2 = _STD _Get_unwrapped_n(_First2, _STD _Idl_distance<_FwdIt1>(_UFirst1, _ULast1)); if constexpr (is_same_v<_Iter_value_t, bool> - && is_same_v<_Iter_value_t, bool> + && is_same_v<_Iter_value_t, bool> // + && _Is_ranges_random_iter_v // + && _Is_ranges_random_iter_v && _Is_any_of_v<_Pr, #if _HAS_CXX20 _RANGES equal_to, #endif equal_to<>, equal_to>) { - if constexpr (!_Is_vb_iterator && !_Is_vb_iterator) { - auto _Pair = _STD mismatch(_UFirst1, _ULast1, _UFirst2); - _UFirst1 = _STD move(_Pair.first); - _UFirst2 = _STD move(_Pair.second); - - if (_UFirst1 == _ULast1) { - return true; - } - } - - return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _UFirst2 + (_ULast1 - _UFirst1), true); + return _STD _Is_permutation_of_bool(_UFirst1, _ULast1, _UFirst2, _UFirst2 + (_ULast1 - _UFirst1)); } for (;; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix @@ -1324,17 +1331,7 @@ _NODISCARD _CONSTEXPR20 bool is_permutation( _RANGES equal_to, #endif equal_to<>, equal_to>) { - if constexpr (!_Is_vb_iterator && !_Is_vb_iterator) { - auto _Pair = _STD mismatch(_UFirst1, _ULast1, _UFirst2); - _UFirst1 = _STD move(_Pair.first); - _UFirst2 = _STD move(_Pair.second); - - if (_UFirst1 == _ULast1) { - return true; - } - } - - return _STD count(_UFirst1, _ULast1, true) == _STD count(_UFirst2, _ULast2, true); + return _STD _Is_permutation_of_bool(_UFirst1, _ULast1, _UFirst2, _ULast2); } for (; _UFirst1 != _ULast1; ++_UFirst1, (void) ++_UFirst2) { // trim matching prefix From 21888e27885a6b94e8d8f858e79126e9c68fb8f1 Mon Sep 17 00:00:00 2001 From: Alex Guteniev Date: Sun, 8 Mar 2026 12:18:18 +0200 Subject: [PATCH 9/9] Expand coverage --- .../test.cpp | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp index 196d8eaa09..67cab7cfb7 100644 --- a/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp +++ b/tests/std/tests/GH_000625_vector_bool_optimization/test.cpp @@ -1519,25 +1519,44 @@ CONSTEXPR20 bool test_copy_part_2() { } CONSTEXPR20 bool test_is_permutation() { - const vector source = { + constexpr bool perm_source_raw[] = { false, true, false, false, false, true, false, true, true, false, false, true, false, false, true, true}; + const vector perm_source(begin(perm_source_raw), end(perm_source_raw)); // Actually a permutation - const vector perm = { + constexpr bool perm_raw[] = { true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true}; - assert(is_permutation(source.begin(), source.end(), perm.begin())); - assert(is_permutation(source.begin(), source.end(), perm.begin(), perm.end())); + const vector perm(begin(perm_raw), end(perm_raw)); + assert(is_permutation(perm_source.begin(), perm_source.end(), perm.begin())); + assert(is_permutation(perm_source.begin(), perm_source.end(), perm.begin(), perm.end())); + assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), perm.begin())); + assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), perm.begin(), perm.end())); + assert(is_permutation(perm_source.begin(), perm_source.end(), begin(perm_raw))); + assert(is_permutation(perm_source.begin(), perm_source.end(), begin(perm_raw), end(perm_raw))); + assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(perm_raw))); + assert(is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(perm_raw), end(perm_raw))); // One extra true value - const vector extra_true = { + constexpr bool extra_true_raw[] = { true, false, true, false, false, true, false, false, true, true, true, false, true, false, false, true}; - assert(!is_permutation(source.begin(), source.end(), extra_true.begin())); - assert(!is_permutation(source.begin(), source.end(), extra_true.begin(), extra_true.end())); + const vector extra_true(begin(extra_true_raw), end(extra_true_raw)); + assert(!is_permutation(perm_source.begin(), perm_source.end(), extra_true.begin())); + assert(!is_permutation(perm_source.begin(), perm_source.end(), extra_true.begin(), extra_true.end())); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), extra_true.begin())); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), extra_true.begin(), extra_true.end())); + assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(extra_true_raw))); + assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(extra_true_raw), end(extra_true_raw))); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(extra_true_raw))); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(extra_true_raw), end(extra_true_raw))); // One element longer - const vector longer = { + constexpr bool longer_raw[] = { true, false, true, false, false, true, false, false, true, false, true, false, true, false, false, true, false}; - assert(!is_permutation(source.begin(), source.end(), longer.begin(), longer.end())); + const vector longer(begin(longer_raw), end(longer_raw)); + assert(!is_permutation(perm_source.begin(), perm_source.end(), longer.begin(), longer.end())); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), longer.begin(), longer.end())); + assert(!is_permutation(perm_source.begin(), perm_source.end(), begin(longer_raw), end(longer_raw))); + assert(!is_permutation(begin(perm_source_raw), end(perm_source_raw), begin(longer_raw), end(longer_raw))); return true; }