diff --git a/docs/matchers.md b/docs/matchers.md index a27bbddd..b89006a9 100644 --- a/docs/matchers.md +++ b/docs/matchers.md @@ -497,8 +497,11 @@ Some (but not all) of Catch2's generic matchers support `constexpr` matching. Currently, this includes: * `IsEmpty()` -* `SizeIs(size_t target_size)` -* `SizeIs(Matcher size_matcher)` +* `SizeIs(size_t target_size)`, `SizeIs(Matcher size_matcher)` +* `AllMatch(Matcher element_matcher)` +* `AnyMatch(Matcher element_matcher)` +* `NoneMatch(Matcher element_matcher)` +* `AllTrue()`, `AnyTrue()`, `NoneTrue()` --- diff --git a/src/catch2/matchers/catch_matchers_quantifiers.cpp b/src/catch2/matchers/catch_matchers_quantifiers.cpp index 5a752467..aeb1cc40 100644 --- a/src/catch2/matchers/catch_matchers_quantifiers.cpp +++ b/src/catch2/matchers/catch_matchers_quantifiers.cpp @@ -9,16 +9,12 @@ namespace Catch { namespace Matchers { - std::string AllTrueMatcher::describe() const { return "contains only true"; } - AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } + std::string AllTrueMatcher::describe() const { return "contains only true"; } std::string NoneTrueMatcher::describe() const { return "contains no true"; } - NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } - std::string AnyTrueMatcher::describe() const { return "contains at least one true"; } - AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } } // namespace Matchers } // namespace Catch diff --git a/src/catch2/matchers/catch_matchers_quantifiers.hpp b/src/catch2/matchers/catch_matchers_quantifiers.hpp index 977b0c76..56684648 100644 --- a/src/catch2/matchers/catch_matchers_quantifiers.hpp +++ b/src/catch2/matchers/catch_matchers_quantifiers.hpp @@ -18,7 +18,7 @@ namespace Catch { class AllMatchMatcher final : public MatcherGenericBase { Matcher m_matcher; public: - AllMatchMatcher(Matcher matcher): + constexpr AllMatchMatcher(Matcher matcher): m_matcher(CATCH_MOVE(matcher)) {} @@ -27,7 +27,7 @@ namespace Catch { } template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (!m_matcher.match(elem)) { return false; @@ -42,7 +42,7 @@ namespace Catch { class NoneMatchMatcher final : public MatcherGenericBase { Matcher m_matcher; public: - NoneMatchMatcher(Matcher matcher): + constexpr NoneMatchMatcher(Matcher matcher): m_matcher(CATCH_MOVE(matcher)) {} @@ -51,7 +51,7 @@ namespace Catch { } template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (m_matcher.match(elem)) { return false; @@ -66,7 +66,7 @@ namespace Catch { class AnyMatchMatcher final : public MatcherGenericBase { Matcher m_matcher; public: - AnyMatchMatcher(Matcher matcher): + constexpr AnyMatchMatcher(Matcher matcher): m_matcher(CATCH_MOVE(matcher)) {} @@ -75,7 +75,7 @@ namespace Catch { } template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (m_matcher.match(elem)) { return true; @@ -91,7 +91,7 @@ namespace Catch { std::string describe() const override; template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (!elem) { return false; @@ -107,7 +107,7 @@ namespace Catch { std::string describe() const override; template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (elem) { return false; @@ -123,7 +123,7 @@ namespace Catch { std::string describe() const override; template - bool match(RangeLike&& rng) const { + constexpr bool match(RangeLike&& rng) const { for (auto&& elem : rng) { if (elem) { return true; @@ -133,32 +133,35 @@ namespace Catch { } }; - // Creates a matcher that checks whether all elements in a range match a matcher + //! Creates a matcher that checks whether all elements in a range match a matcher template - AllMatchMatcher AllMatch(Matcher&& matcher) { + constexpr AllMatchMatcher AllMatch(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } - // Creates a matcher that checks whether no element in a range matches a matcher. + //! Creates a matcher that checks whether no element in a range matches a matcher. template - NoneMatchMatcher NoneMatch(Matcher&& matcher) { + constexpr NoneMatchMatcher NoneMatch(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } - // Creates a matcher that checks whether any element in a range matches a matcher. + //! Creates a matcher that checks whether any element in a range matches a matcher. template - AnyMatchMatcher AnyMatch(Matcher&& matcher) { + constexpr AnyMatchMatcher AnyMatch(Matcher&& matcher) { return { CATCH_FORWARD(matcher) }; } - // Creates a matcher that checks whether all elements in a range are true - AllTrueMatcher AllTrue(); + //! Creates a matcher that checks whether all elements in a range are true + inline CATCH_DESTRUCTOR_CONSTEXPR + AllTrueMatcher AllTrue() { return AllTrueMatcher{}; } - // Creates a matcher that checks whether no element in a range is true - NoneTrueMatcher NoneTrue(); + //! Creates a matcher that checks whether no element in a range is true + inline CATCH_DESTRUCTOR_CONSTEXPR + NoneTrueMatcher NoneTrue() { return NoneTrueMatcher{}; } - // Creates a matcher that checks whether any element in a range is true - AnyTrueMatcher AnyTrue(); + //! Creates a matcher that checks whether any element in a range is true + inline CATCH_DESTRUCTOR_CONSTEXPR + AnyTrueMatcher AnyTrue() { return AnyTrueMatcher{}; } } } diff --git a/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp index 0d910daf..66fe7c8f 100644 --- a/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp +++ b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp @@ -6,14 +6,17 @@ // SPDX-License-Identifier: BSL-1.0 +#include + +#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED ) + #include #include #include +#include #include -#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED ) - namespace { struct MatchAllMatcher final : public Catch::Matchers::MatcherGenericBase { public: @@ -30,6 +33,21 @@ namespace { constexpr MatchAllMatcher MatchAll() { return MatchAllMatcher(); } + struct MatchTrueMatcher final : public Catch::Matchers::MatcherGenericBase { + public: + constexpr bool match( bool b ) const { + return b; + } + + std::string describe() const override { + using namespace std::string_literals; + return "Matches true"s; + } + }; + + constexpr MatchTrueMatcher MatchTrue() { return MatchTrueMatcher(); } + + } // namespace TEST_CASE( "Constexpr support for matchers", "[constexpr][matchers][approvals]" ) { @@ -39,11 +57,37 @@ TEST_CASE( "Constexpr support for matchers", "[constexpr][matchers][approvals]" TEST_CASE( "IsEmpty and HasSize matchers can be used in constexpr contexts", "[constexpr][matchers][approvals]" ){ + using namespace Catch::Matchers; static constexpr std::array empty{}; - STATIC_REQUIRE_THAT( empty, Catch::Matchers::IsEmpty() ); + STATIC_REQUIRE_THAT( empty, IsEmpty() ); static constexpr int arr[1] = { 2 }; - STATIC_REQUIRE_THAT( arr, Catch::Matchers::SizeIs( 1 ) ); - STATIC_REQUIRE_THAT( arr, Catch::Matchers::SizeIs( MatchAll() ) ); + STATIC_REQUIRE_THAT( arr, SizeIs( 1 ) ); + STATIC_REQUIRE_THAT( arr, SizeIs( MatchAll() ) ); +} + +constexpr std::array compute_bools( int type ) { + switch ( type ) { + case 0: + return { true, true, true }; + case 1: + return { false, true, false }; + case 2: + return { false, false, false }; + default: + return { false, false, false }; + } +} + +TEST_CASE( "Quantifier matchers can be used in constexpr contexts", + "[constexpr][matchers][approvals]" ) { + using namespace Catch::Matchers; + STATIC_REQUIRE_THAT( compute_bools( 0 ), AllTrue() ); + STATIC_REQUIRE_THAT( compute_bools( 1 ), AnyTrue() ); + STATIC_REQUIRE_THAT( compute_bools( 2 ), NoneTrue() ); + + STATIC_REQUIRE_THAT( compute_bools( 0 ), AllMatch( MatchTrue() ) ); + STATIC_REQUIRE_THAT( compute_bools( 1 ), AnyMatch( MatchTrue() ) ); + STATIC_REQUIRE_THAT( compute_bools( 2 ), NoneMatch( MatchTrue() ) ); } // Combining matchers needs C++26 and P2738, so they are in separate preprocessor block