From 112c8ec530e12d350b7d6513e3cbda5a69071caf Mon Sep 17 00:00:00 2001 From: puneetdixit200 <236133619+puneetdixit200@users.noreply.github.com> Date: Fri, 22 May 2026 14:03:58 +0530 Subject: [PATCH 1/2] Keep unordered range comparator order --- .../matchers/catch_matchers_range_equals.hpp | 36 ++++++++++++++--- .../Baselines/compact.sw.approved.txt | 3 +- .../Baselines/compact.sw.multi.approved.txt | 3 +- .../Baselines/console.std.approved.txt | 2 +- .../Baselines/console.sw.approved.txt | 15 ++++++- .../Baselines/console.sw.multi.approved.txt | 15 ++++++- .../SelfTest/Baselines/junit.sw.approved.txt | 3 +- .../Baselines/junit.sw.multi.approved.txt | 3 +- .../Baselines/sonarqube.sw.approved.txt | 1 + .../Baselines/sonarqube.sw.multi.approved.txt | 1 + tests/SelfTest/Baselines/tap.sw.approved.txt | 4 +- .../Baselines/tap.sw.multi.approved.txt | 4 +- tests/SelfTest/Baselines/xml.sw.approved.txt | 16 +++++++- .../Baselines/xml.sw.multi.approved.txt | 16 +++++++- .../UsageTests/MatchersRanges.tests.cpp | 40 ++++++++++++++++++- 15 files changed, 144 insertions(+), 18 deletions(-) diff --git a/src/catch2/matchers/catch_matchers_range_equals.hpp b/src/catch2/matchers/catch_matchers_range_equals.hpp index 11a1f2ffe3..a224ff3ea7 100644 --- a/src/catch2/matchers/catch_matchers_range_equals.hpp +++ b/src/catch2/matchers/catch_matchers_range_equals.hpp @@ -12,6 +12,7 @@ #include #include +#include namespace Catch { namespace Matchers { @@ -74,15 +75,38 @@ namespace Catch { m_predicate( CATCH_FORWARD( predicate ) ) {} template - constexpr bool match( RangeLike&& rng ) const { using std::begin; using std::end; - return Catch::Detail::is_permutation( begin( m_desired ), - end( m_desired ), - begin( rng ), - end( rng ), - m_predicate ); + + const auto target_begin = begin( m_desired ); + const auto target_end = end( m_desired ); + const auto target_size = static_cast( + Catch::Detail::sentinel_distance( target_begin, + target_end ) ); + std::vector matched( target_size, false ); + + size_t matched_count = 0; + for ( auto&& element : rng ) { + if ( matched_count == target_size ) { return false; } + + auto target = target_begin; + bool found_match = false; + for ( size_t index = 0; target != target_end; + ++target, ++index ) { + if ( !matched[index] && + m_predicate( element, *target ) ) { + matched[index] = true; + ++matched_count; + found_match = true; + break; + } + } + + if ( !found_match ) { return false; } + } + + return matched_count == target_size; } std::string describe() const override { diff --git a/tests/SelfTest/Baselines/compact.sw.approved.txt b/tests/SelfTest/Baselines/compact.sw.approved.txt index cb90a0f559..974d219952 100644 --- a/tests/SelfTest/Baselines/compact.sw.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.approved.txt @@ -2440,6 +2440,7 @@ MatchersRanges.tests.cpp:: passed: array_a, !UnorderedRangeEquals( MatchersRanges.tests.cpp:: passed: vector_a, !UnorderedRangeEquals( vector_b ) for: { 1, 2, 3 } not unordered elements are { 1, 2, 3, 4 } MatchersRanges.tests.cpp:: passed: vector_a, UnorderedRangeEquals( vector_a_plus_1, close_enough ) for: { 1, 10, 20 } unordered elements are { 11, 21, 2 } MatchersRanges.tests.cpp:: passed: vector_a, !UnorderedRangeEquals( vector_b, close_enough ) for: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } +MatchersRanges.tests.cpp:: passed: actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) for: { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } MatchersRanges.tests.cpp:: passed: needs_adl1, UnorderedRangeEquals( needs_adl2 ) for: { 1, 2, 3, 4, 5 } unordered elements are { 1, 2, 3, 4, 5 } MatchersRanges.tests.cpp:: passed: array_a, UnorderedRangeEquals( { 10, 20, 1 } ) for: { 1, 10, 20 } unordered elements are { 10, 20, 1 } MatchersRanges.tests.cpp:: passed: array_a, UnorderedRangeEquals( { 11, 21, 2 }, []( int l, int r ) { return std::abs( l - r ) <= 1; } ) for: { 1, 10, 20 } unordered elements are { 11, 21, 2 } @@ -3001,6 +3002,6 @@ InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt index 39c260afd4..dce295370f 100644 --- a/tests/SelfTest/Baselines/compact.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/compact.sw.multi.approved.txt @@ -2433,6 +2433,7 @@ MatchersRanges.tests.cpp:: passed: array_a, !UnorderedRangeEquals( MatchersRanges.tests.cpp:: passed: vector_a, !UnorderedRangeEquals( vector_b ) for: { 1, 2, 3 } not unordered elements are { 1, 2, 3, 4 } MatchersRanges.tests.cpp:: passed: vector_a, UnorderedRangeEquals( vector_a_plus_1, close_enough ) for: { 1, 10, 20 } unordered elements are { 11, 21, 2 } MatchersRanges.tests.cpp:: passed: vector_a, !UnorderedRangeEquals( vector_b, close_enough ) for: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } +MatchersRanges.tests.cpp:: passed: actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) for: { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } MatchersRanges.tests.cpp:: passed: needs_adl1, UnorderedRangeEquals( needs_adl2 ) for: { 1, 2, 3, 4, 5 } unordered elements are { 1, 2, 3, 4, 5 } MatchersRanges.tests.cpp:: passed: array_a, UnorderedRangeEquals( { 10, 20, 1 } ) for: { 1, 10, 20 } unordered elements are { 10, 20, 1 } MatchersRanges.tests.cpp:: passed: array_a, UnorderedRangeEquals( { 11, 21, 2 }, []( int l, int r ) { return std::abs( l - r ) <= 1; } ) for: { 1, 10, 20 } unordered elements are { 11, 21, 2 } @@ -2990,6 +2991,6 @@ InternalBenchmark.tests.cpp:: passed: q3 == 23. for: 23.0 == 23.0 Misc.tests.cpp:: passed: Misc.tests.cpp:: passed: test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.std.approved.txt b/tests/SelfTest/Baselines/console.std.approved.txt index 78c3d64051..1341660973 100644 --- a/tests/SelfTest/Baselines/console.std.approved.txt +++ b/tests/SelfTest/Baselines/console.std.approved.txt @@ -1744,5 +1744,5 @@ due to unexpected exception with message: =============================================================================== test cases: 451 | 349 passed | 76 failed | 7 skipped | 19 failed as expected -assertions: 2394 | 2215 passed | 136 failed | 43 failed as expected +assertions: 2395 | 2216 passed | 136 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.approved.txt b/tests/SelfTest/Baselines/console.sw.approved.txt index add1eaaf51..e25eceae2e 100644 --- a/tests/SelfTest/Baselines/console.sw.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.approved.txt @@ -16185,6 +16185,19 @@ MatchersRanges.tests.cpp:: PASSED: with expansion: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } +------------------------------------------------------------------------------- +Usage of UnorderedRangeEquals range matcher + Custom predicate + Different element types keep predicate argument order +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + CHECK_THAT( actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) ) +with expansion: + { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } + ------------------------------------------------------------------------------- Usage of UnorderedRangeEquals range matcher Ranges that need ADL begin/end @@ -20135,5 +20148,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/console.sw.multi.approved.txt b/tests/SelfTest/Baselines/console.sw.multi.approved.txt index 071955777d..1b2a3edf58 100644 --- a/tests/SelfTest/Baselines/console.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/console.sw.multi.approved.txt @@ -16178,6 +16178,19 @@ MatchersRanges.tests.cpp:: PASSED: with expansion: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } +------------------------------------------------------------------------------- +Usage of UnorderedRangeEquals range matcher + Custom predicate + Different element types keep predicate argument order +------------------------------------------------------------------------------- +MatchersRanges.tests.cpp: +............................................................................... + +MatchersRanges.tests.cpp:: PASSED: + CHECK_THAT( actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) ) +with expansion: + { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } + ------------------------------------------------------------------------------- Usage of UnorderedRangeEquals range matcher Ranges that need ADL begin/end @@ -20124,5 +20137,5 @@ Misc.tests.cpp:: PASSED: =============================================================================== test cases: 451 | 331 passed | 96 failed | 6 skipped | 18 failed as expected -assertions: 2416 | 2215 passed | 158 failed | 43 failed as expected +assertions: 2417 | 2216 passed | 158 failed | 43 failed as expected diff --git a/tests/SelfTest/Baselines/junit.sw.approved.txt b/tests/SelfTest/Baselines/junit.sw.approved.txt index 328b466c0e..44bbc7495d 100644 --- a/tests/SelfTest/Baselines/junit.sw.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.approved.txt @@ -1,7 +1,7 @@ - + @@ -1417,6 +1417,7 @@ at Message.tests.cpp: + diff --git a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt index 403ed251b7..fa3da4453d 100644 --- a/tests/SelfTest/Baselines/junit.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/junit.sw.multi.approved.txt @@ -1,6 +1,6 @@ - + @@ -1416,6 +1416,7 @@ at Message.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt index f9e851fc14..797fef6c8c 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.approved.txt @@ -1709,6 +1709,7 @@ at Matchers.tests.cpp: + diff --git a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt index e1ede12c12..dde043155f 100644 --- a/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/sonarqube.sw.multi.approved.txt @@ -1708,6 +1708,7 @@ at Matchers.tests.cpp: + diff --git a/tests/SelfTest/Baselines/tap.sw.approved.txt b/tests/SelfTest/Baselines/tap.sw.approved.txt index 7624d1406e..63c85e2af2 100644 --- a/tests/SelfTest/Baselines/tap.sw.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.approved.txt @@ -3888,6 +3888,8 @@ ok {test-number} - vector_a, UnorderedRangeEquals( vector_a_plus_1, close_enough # Usage of UnorderedRangeEquals range matcher ok {test-number} - vector_a, !UnorderedRangeEquals( vector_b, close_enough ) for: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } # Usage of UnorderedRangeEquals range matcher +ok {test-number} - actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) for: { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } +# Usage of UnorderedRangeEquals range matcher ok {test-number} - needs_adl1, UnorderedRangeEquals( needs_adl2 ) for: { 1, 2, 3, 4, 5 } unordered elements are { 1, 2, 3, 4, 5 } # Usage of UnorderedRangeEquals range matcher ok {test-number} - array_a, UnorderedRangeEquals( { 10, 20, 1 } ) for: { 1, 10, 20 } unordered elements are { 10, 20, 1 } @@ -4851,5 +4853,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2428 +1..2429 diff --git a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt index ed653421b1..9746f772ac 100644 --- a/tests/SelfTest/Baselines/tap.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/tap.sw.multi.approved.txt @@ -3881,6 +3881,8 @@ ok {test-number} - vector_a, UnorderedRangeEquals( vector_a_plus_1, close_enough # Usage of UnorderedRangeEquals range matcher ok {test-number} - vector_a, !UnorderedRangeEquals( vector_b, close_enough ) for: { 1, 10, 21 } not unordered elements are { 11, 21, 3 } # Usage of UnorderedRangeEquals range matcher +ok {test-number} - actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) for: { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } +# Usage of UnorderedRangeEquals range matcher ok {test-number} - needs_adl1, UnorderedRangeEquals( needs_adl2 ) for: { 1, 2, 3, 4, 5 } unordered elements are { 1, 2, 3, 4, 5 } # Usage of UnorderedRangeEquals range matcher ok {test-number} - array_a, UnorderedRangeEquals( { 10, 20, 1 } ) for: { 1, 10, 20 } unordered elements are { 10, 20, 1 } @@ -4840,5 +4842,5 @@ ok {test-number} - q3 == 23. for: 23.0 == 23.0 ok {test-number} - # xmlentitycheck ok {test-number} - -1..2428 +1..2429 diff --git a/tests/SelfTest/Baselines/xml.sw.approved.txt b/tests/SelfTest/Baselines/xml.sw.approved.txt index 933094d34c..7fe5899388 100644 --- a/tests/SelfTest/Baselines/xml.sw.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.approved.txt @@ -18919,6 +18919,20 @@ There is no extra whitespace here +
+
+ + + actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) + + + { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } + + + +
+ +
@@ -23385,6 +23399,6 @@ Approx( -1.95996398454005449 )
- + diff --git a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt index 646f0c72f9..c2d6fba835 100644 --- a/tests/SelfTest/Baselines/xml.sw.multi.approved.txt +++ b/tests/SelfTest/Baselines/xml.sw.multi.approved.txt @@ -18919,6 +18919,20 @@ There is no extra whitespace here +
+
+ + + actual, UnorderedRangeEquals( expected, []( UnorderedRangeActual const& lhs, UnorderedRangeExpected const& rhs ) { return lhs.value == rhs.value; } ) + + + { 1, 2, 3, 4 } unordered elements are { 4, 2, 3, 1 } + + + +
+ +
@@ -23384,6 +23398,6 @@ Approx( -1.95996398454005449 )
- + diff --git a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp index 4f906b99af..78200fb038 100644 --- a/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp +++ b/tests/SelfTest/UsageTests/MatchersRanges.tests.cpp @@ -41,6 +41,30 @@ struct MoveOnlyTestElement { } }; +namespace { + + struct UnorderedRangeActual { + int value; + }; + + struct UnorderedRangeExpected { + int value; + }; + + std::ostream& operator<<( std::ostream& out, + UnorderedRangeActual const& value ) { + out << value.value; + return out; + } + + std::ostream& operator<<( std::ostream& out, + UnorderedRangeExpected const& value ) { + out << value.value; + return out; + } + +} // end unnamed namespace + TEST_CASE("Basic use of the Contains range matcher", "[matchers][templated][contains]") { using Catch::Matchers::Contains; @@ -819,6 +843,20 @@ TEST_CASE( "Usage of UnorderedRangeEquals range matcher", CHECK_THAT( vector_a, !UnorderedRangeEquals( vector_b, close_enough ) ); } + SECTION( "Different element types keep predicate argument order" ) { + const std::vector actual{ + { 1 }, { 2 }, { 3 }, { 4 } }; + const std::vector expected{ + { 4 }, { 2 }, { 3 }, { 1 } }; + + CHECK_THAT( actual, + UnorderedRangeEquals( + expected, + []( UnorderedRangeActual const& lhs, + UnorderedRangeExpected const& rhs ) { + return lhs.value == rhs.value; + } ) ); + } } @@ -933,4 +971,4 @@ TEST_CASE( "Type conversions of RangeEquals and similar", UnorderedRangeEquals( array_a_plus_1, close_enough ) ); } } -} \ No newline at end of file +} From 55fb808878de71f48033964acd40b823f545d8c7 Mon Sep 17 00:00:00 2001 From: Puneet Dixit <236133619+puneetdixit200@users.noreply.github.com> Date: Fri, 22 May 2026 16:13:05 +0530 Subject: [PATCH 2/2] Fix constexpr unordered range matcher --- src/catch2/matchers/catch_matchers_range_equals.hpp | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/catch2/matchers/catch_matchers_range_equals.hpp b/src/catch2/matchers/catch_matchers_range_equals.hpp index a224ff3ea7..a0d72ecc6b 100644 --- a/src/catch2/matchers/catch_matchers_range_equals.hpp +++ b/src/catch2/matchers/catch_matchers_range_equals.hpp @@ -75,6 +75,9 @@ namespace Catch { m_predicate( CATCH_FORWARD( predicate ) ) {} template +#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED ) + constexpr +#endif bool match( RangeLike&& rng ) const { using std::begin; using std::end;