Read-only mirrors. Development, issues and pull requests are on GitHub.

summaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorMartin Hořeňovský2026-05-10 23:05:46 +0200
committerMartin Hořeňovský2026-05-12 11:05:02 +0200
commit651247c7f40eda6846fec736d2f9edac8949dacb (patch)
tree76b4ef9f6d100c787f6b8af93779c43ea9524f67
parent15b9393f0f32327e28833c5032b2c4e02c31d82c (diff)
downloadcatch3-651247c7f40eda6846fec736d2f9edac8949dacb.tar.gz
Support for constexpr matchers in C++20 (P0784)
To make this all work, I had to remove the stringification cache from matchers. In theory, this can cause performance penalty in cases where single matcher instance is stringified multiple times, but in practice this does not happen much, and the difference is surprisingly small anyway, because the performance of stringification is already horrible and full of allocating strings just to throw them away. The matcher combinators need P2738 from C++26 to be `constexpr`. Closes #3091
-rw-r--r--docs/matchers.md21
-rw-r--r--docs/other-macros.md24
-rw-r--r--src/catch2/internal/catch_compiler_capabilities.hpp12
-rw-r--r--src/catch2/matchers/catch_matchers.cpp10
-rw-r--r--src/catch2/matchers/catch_matchers.hpp44
-rw-r--r--src/catch2/matchers/catch_matchers_templated.cpp6
-rw-r--r--src/catch2/matchers/catch_matchers_templated.hpp99
-rw-r--r--tests/BUILD.bazel1
-rw-r--r--tests/CMakeLists.txt3
-rw-r--r--tests/ExtraTests/CMakeLists.txt2
-rw-r--r--tests/ExtraTests/X02-DisabledMacros.cpp2
-rw-r--r--tests/ExtraTests/X05-DeferredStaticChecks.cpp26
-rw-r--r--tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp52
-rw-r--r--tests/meson.build1
14 files changed, 254 insertions, 49 deletions
diff --git a/docs/matchers.md b/docs/matchers.md
index 4b9445ae..a013245c 100644
--- a/docs/matchers.md
+++ b/docs/matchers.md
@@ -6,6 +6,7 @@
[Built-in matchers](#built-in-matchers)<br>
[Writing custom matchers (old style)](#writing-custom-matchers-old-style)<br>
[Writing custom matchers (new style)](#writing-custom-matchers-new-style)<br>
+[Constexpr matchers](#constexpr-matchers)<br>
Matchers, as popularized by the [Hamcrest](https://en.wikipedia.org/wiki/Hamcrest)
framework are an alternative way to write assertions, useful for tests
@@ -471,6 +472,26 @@ and new style matchers arbitrarily.
> `MatcherGenericBase` lives in `catch2/matchers/catch_matchers_templated.hpp`
+## Constexpr matchers
+
+> Support for constexpr matchers was introduced in Catch2 vX.Y.Z
+
+When compiled for C++20, the new-style matchers (can) support `constexpr`
+matching, albeit not `constexpr` stringification. The matcher combinators
+require C++26 (or at least P2738) to be `constexpr` compatible.
+
+This can be used together with the `STATIC_REQUIRE_THAT` macro to write
+matcher-based static assertions like this:
+
+```cpp
+TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") {
+ STATIC_REQUIRE_THAT( 1, MatchAll() );
+ STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() );
+ STATIC_REQUIRE_THAT( 1, !!MatchAll() );
+}
+```
+
+
---
[Home](Readme.md#top)
diff --git a/docs/other-macros.md b/docs/other-macros.md
index 79990a6a..de7e4bc6 100644
--- a/docs/other-macros.md
+++ b/docs/other-macros.md
@@ -91,6 +91,30 @@ TEST_CASE("STATIC_CHECK showcase", "[traits]") {
}
```
+* `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT`
+
+> `STATIC_REQUIRE_THAT` and `STATIC_CHECK_THAT` was introduced in Catch2 X.Y.Z
+
+`STATIC_{REQUIRE,CHECK}_THAT` are analogous to `STATIC_{REQUIRE,CHECK}`,
+but for matchers. They are always defined, even if the current compiler
+does not support `constexpr` matchers, but in that case the compilation
+will always fail.
+
+Just like `STATIC_{REQUIRE,CHECK}`, `STATIC_{REQUIRE,CHECK}_THAT` can be
+delayed into runtime through the `CATCH_CONFIG_RUNTIME_STATIC_REQUIRE`
+configuration option.
+
+Example:
+```cpp
+TEST_CASE("Constexpr support for matchers", "[constexpr][matchers]") {
+ STATIC_REQUIRE_THAT( 1, MatchAll() );
+ STATIC_REQUIRE_THAT( 1, MatchAll() && MatchAll() );
+ STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() );
+ STATIC_REQUIRE_THAT( 1, !!MatchAll() );
+}
+```
+
+
## Test case related macros
* `REGISTER_TEST_CASE`
diff --git a/src/catch2/internal/catch_compiler_capabilities.hpp b/src/catch2/internal/catch_compiler_capabilities.hpp
index d0c79243..2d6d0c75 100644
--- a/src/catch2/internal/catch_compiler_capabilities.hpp
+++ b/src/catch2/internal/catch_compiler_capabilities.hpp
@@ -35,6 +35,18 @@
# define CATCH_CPP20_OR_GREATER
#endif
+// Matchers are only constexpr-able in C++20
+#if defined( CATCH_CPP20_OR_GREATER ) && \
+ defined( __cpp_constexpr_dynamic_alloc ) && \
+ __cpp_constexpr_dynamic_alloc >= 201907L && \
+ /* GCC < 13 define the feature macro, but compiler bugs stop us from using it */ \
+ ( !defined( __GNUC__ ) || __GNUC__ >= 13 || defined(__clang__) )
+# define CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED
+# define CATCH_DESTRUCTOR_CONSTEXPR constexpr
+#else
+# define CATCH_DESTRUCTOR_CONSTEXPR
+#endif
+
// Only GCC compiler should be used in this block, so other compilers trying to
// mask themselves as GCC should be ignored.
#if defined(__GNUC__) && !defined(__clang__) && !defined(__ICC) && !defined(__CUDACC__) && !defined(__LCC__) && !defined(__NVCOMPILER)
diff --git a/src/catch2/matchers/catch_matchers.cpp b/src/catch2/matchers/catch_matchers.cpp
index 123b3041..d1add581 100644
--- a/src/catch2/matchers/catch_matchers.cpp
+++ b/src/catch2/matchers/catch_matchers.cpp
@@ -13,13 +13,13 @@ namespace Catch {
namespace Matchers {
std::string MatcherUntypedBase::toString() const {
- if (m_cachedToString.empty()) {
- m_cachedToString = describe();
- }
- return m_cachedToString;
+ return describe();
}
- MatcherUntypedBase::~MatcherUntypedBase() = default;
+ std::string MatcherUntypedBase::describe() const {
+ using namespace std::string_literals;
+ return "Undescribed matcher"s;
+ }
} // namespace Matchers
} // namespace Catch
diff --git a/src/catch2/matchers/catch_matchers.hpp b/src/catch2/matchers/catch_matchers.hpp
index 90ed3338..6d30c7f0 100644
--- a/src/catch2/matchers/catch_matchers.hpp
+++ b/src/catch2/matchers/catch_matchers.hpp
@@ -20,10 +20,10 @@ namespace Matchers {
class MatcherUntypedBase {
public:
- MatcherUntypedBase() = default;
+ constexpr MatcherUntypedBase() = default;
- MatcherUntypedBase(MatcherUntypedBase const&) = default;
- MatcherUntypedBase(MatcherUntypedBase&&) = default;
+ constexpr MatcherUntypedBase(MatcherUntypedBase const&) = default;
+ constexpr MatcherUntypedBase(MatcherUntypedBase&&) = default;
MatcherUntypedBase& operator = (MatcherUntypedBase const&) = delete;
MatcherUntypedBase& operator = (MatcherUntypedBase&&) = delete;
@@ -31,9 +31,9 @@ namespace Matchers {
std::string toString() const;
protected:
- virtual ~MatcherUntypedBase(); // = default;
- virtual std::string describe() const = 0;
- mutable std::string m_cachedToString;
+ CATCH_DESTRUCTOR_CONSTEXPR virtual ~MatcherUntypedBase() = default;
+ //! Should be overridden, but we provide default "undescribed" impl
+ virtual std::string describe() const;
};
@@ -215,6 +215,19 @@ namespace Matchers {
#define CATCH_CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
#define CATCH_REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CATCH_REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+ #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+ #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) \
+ static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
+ CATCH_SUCCEED( #matcher ".match( " #arg " )" )
+ #define CATCH_STATIC_CHECK_THAT( arg, matcher ) \
+ static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
+ CATCH_SUCCEED( #matcher ".match( " #arg " )" )
+ #else
+ #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) CATCH_REQUIRE_THAT( arg, matcher )
+ #define CATCH_STATIC_CHECK_THAT( arg, matcher ) CATCH_CHECK_THAT( arg, matcher )
+ #endif
+
+
#elif defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#define CATCH_REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
@@ -226,6 +239,9 @@ namespace Matchers {
#define CATCH_CHECK_THAT( arg, matcher ) (void)(0)
#define CATCH_REQUIRE_THAT( arg, matcher ) (void)(0)
+ #define CATCH_STATIC_REQUIRE_THAT( arg, matcher ) (void)(0)
+ #define CATCH_STATIC_CHECK_THAT( arg, matcher ) (void)(0)
+
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && !defined(CATCH_CONFIG_DISABLE)
#define REQUIRE_THROWS_WITH( expr, matcher ) INTERNAL_CATCH_THROWS_STR_MATCHES( "REQUIRE_THROWS_WITH", Catch::ResultDisposition::Normal, matcher, expr )
@@ -237,6 +253,19 @@ namespace Matchers {
#define CHECK_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "CHECK_THAT", matcher, Catch::ResultDisposition::ContinueOnFailure, arg )
#define REQUIRE_THAT( arg, matcher ) INTERNAL_CHECK_THAT( "REQUIRE_THAT", matcher, Catch::ResultDisposition::Normal, arg )
+ #if !defined(CATCH_CONFIG_RUNTIME_STATIC_REQUIRE)
+ #define STATIC_REQUIRE_THAT( arg, matcher ) \
+ static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
+ SUCCEED( #matcher ".match( " #arg " )" )
+ #define STATIC_CHECK_THAT( arg, matcher ) \
+ static_assert( ( matcher ).match( arg ), #matcher ".match( " #arg " )"); \
+ SUCCEED( #matcher ".match( " #arg " )" )
+ #else
+ #define STATIC_REQUIRE_THAT( arg, matcher ) REQUIRE_THAT( arg, matcher )
+ #define STATIC_CHECK_THAT( arg, matcher ) CHECK_THAT( arg, matcher )
+ #endif
+
+
#elif !defined(CATCH_CONFIG_PREFIX_ALL) && defined(CATCH_CONFIG_DISABLE)
#define REQUIRE_THROWS_WITH( expr, matcher ) (void)(0)
@@ -248,6 +277,9 @@ namespace Matchers {
#define CHECK_THAT( arg, matcher ) (void)(0)
#define REQUIRE_THAT( arg, matcher ) (void)(0)
+ #define STATIC_REQUIRE_THAT( arg, matcher ) (void)(0)
+ #define STATIC_CHECK_THAT( arg, matcher ) (void)(0)
+
#endif // end of user facing macro declarations
#endif // CATCH_MATCHERS_HPP_INCLUDED
diff --git a/src/catch2/matchers/catch_matchers_templated.cpp b/src/catch2/matchers/catch_matchers_templated.cpp
index 2fc529d2..e755b479 100644
--- a/src/catch2/matchers/catch_matchers_templated.cpp
+++ b/src/catch2/matchers/catch_matchers_templated.cpp
@@ -9,7 +9,11 @@
namespace Catch {
namespace Matchers {
- MatcherGenericBase::~MatcherGenericBase() = default;
+
+ std::string MatcherGenericBase::describe() const {
+ using namespace std::string_literals;
+ return "Undescribed generic matcher"s;
+ }
namespace Detail {
diff --git a/src/catch2/matchers/catch_matchers_templated.hpp b/src/catch2/matchers/catch_matchers_templated.hpp
index 0cd40163..2c6247f8 100644
--- a/src/catch2/matchers/catch_matchers_templated.hpp
+++ b/src/catch2/matchers/catch_matchers_templated.hpp
@@ -22,12 +22,12 @@
namespace Catch {
namespace Matchers {
class MatcherGenericBase : public MatcherUntypedBase {
+ std::string describe() const override;
public:
- MatcherGenericBase() = default;
- ~MatcherGenericBase() override; // = default;
+ constexpr MatcherGenericBase() = default;
- MatcherGenericBase(MatcherGenericBase const&) = default;
- MatcherGenericBase(MatcherGenericBase&&) = default;
+ constexpr MatcherGenericBase(MatcherGenericBase const&) = default;
+ constexpr MatcherGenericBase(MatcherGenericBase&&) = default;
MatcherGenericBase& operator=(MatcherGenericBase const&) = delete;
MatcherGenericBase& operator=(MatcherGenericBase&&) = delete;
@@ -36,7 +36,9 @@ namespace Matchers {
namespace Detail {
template<std::size_t N, std::size_t M>
- std::array<void const*, N + M> array_cat(std::array<void const*, N> && lhs, std::array<void const*, M> && rhs) {
+ constexpr std::array<void const*, N + M>
+ array_cat( std::array<void const*, N>&& lhs,
+ std::array<void const*, M>&& rhs ) {
std::array<void const*, N + M> arr{};
std::copy_n(lhs.begin(), N, arr.begin());
std::copy_n(rhs.begin(), M, arr.begin() + N);
@@ -44,7 +46,8 @@ namespace Matchers {
}
template<std::size_t N>
- std::array<void const*, N+1> array_cat(std::array<void const*, N> && lhs, void const* rhs) {
+ constexpr std::array<void const*, N + 1>
+ array_cat( std::array<void const*, N>&& lhs, void const* rhs ) {
std::array<void const*, N+1> arr{};
std::copy_n(lhs.begin(), N, arr.begin());
arr[N] = rhs;
@@ -52,7 +55,8 @@ namespace Matchers {
}
template<std::size_t N>
- std::array<void const*, N+1> array_cat(void const* lhs, std::array<void const*, N> && rhs) {
+ constexpr std::array<void const*, N + 1>
+ array_cat( void const* lhs, std::array<void const*, N>&& rhs ) {
std::array<void const*, N + 1> arr{ {lhs} };
std::copy_n(rhs.begin(), N, arr.begin() + 1);
return arr;
@@ -75,23 +79,31 @@ namespace Matchers {
template<std::size_t N, typename Arg>
- bool match_all_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {
+ constexpr bool match_all_of( Arg&&,
+ std::array<void const*, N> const&,
+ std::index_sequence<> ) {
return true;
}
template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>
- bool match_all_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {
+ constexpr bool match_all_of( Arg&& arg,
+ std::array<void const*, N> const& matchers,
+ std::index_sequence<Idx, Indices...> ) {
return static_cast<T const*>(matchers[Idx])->match(arg) && match_all_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});
}
template<std::size_t N, typename Arg>
- bool match_any_of(Arg&&, std::array<void const*, N> const&, std::index_sequence<>) {
+ constexpr bool match_any_of( Arg&&,
+ std::array<void const*, N> const&,
+ std::index_sequence<> ) {
return false;
}
template<typename T, typename... MatcherTs, std::size_t N, typename Arg, std::size_t Idx, std::size_t... Indices>
- bool match_any_of(Arg&& arg, std::array<void const*, N> const& matchers, std::index_sequence<Idx, Indices...>) {
+ constexpr bool match_any_of( Arg&& arg,
+ std::array<void const*, N> const& matchers,
+ std::index_sequence<Idx, Indices...> ) {
return static_cast<T const*>(matchers[Idx])->match(arg) || match_any_of<MatcherTs...>(arg, matchers, std::index_sequence<Indices...>{});
}
@@ -112,15 +124,18 @@ namespace Matchers {
public:
MatchAllOfGeneric(MatchAllOfGeneric const&) = delete;
MatchAllOfGeneric& operator=(MatchAllOfGeneric const&) = delete;
- MatchAllOfGeneric(MatchAllOfGeneric&&) = default;
- MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
+ constexpr MatchAllOfGeneric( MatchAllOfGeneric&& ) = default;
+ constexpr MatchAllOfGeneric& operator=(MatchAllOfGeneric&&) = default;
- MatchAllOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
+ constexpr MatchAllOfGeneric(
+ MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND )
: m_matchers{ {std::addressof(matchers)...} } {}
- explicit MatchAllOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
+ constexpr explicit MatchAllOfGeneric(
+ std::array<void const*, sizeof...( MatcherTs )> matchers ):
+ m_matchers{ matchers } {}
template<typename Arg>
- bool match(Arg&& arg) const {
+ constexpr bool match( Arg&& arg ) const {
return match_all_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});
}
@@ -136,7 +151,7 @@ namespace Matchers {
//! Avoids type nesting for `GenericAllOf && GenericAllOf` case
template<typename... MatchersRHS>
- friend
+ constexpr friend
MatchAllOfGeneric<MatcherTs..., MatchersRHS...> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
@@ -145,7 +160,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAllOf && some matcher` case
template<typename MatcherRHS>
- friend std::enable_if_t<is_matcher_v<MatcherRHS>,
+ constexpr friend std::enable_if_t<
+ is_matcher_v<MatcherRHS>,
MatchAllOfGeneric<MatcherTs..., MatcherRHS>> operator && (
MatchAllOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
@@ -154,7 +170,8 @@ namespace Matchers {
//! Avoids type nesting for `some matcher && GenericAllOf` case
template<typename MatcherLHS>
- friend std::enable_if_t<is_matcher_v<MatcherLHS>,
+ constexpr friend std::enable_if_t<
+ is_matcher_v<MatcherLHS>,
MatchAllOfGeneric<MatcherLHS, MatcherTs...>> operator && (
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAllOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
@@ -168,15 +185,18 @@ namespace Matchers {
public:
MatchAnyOfGeneric(MatchAnyOfGeneric const&) = delete;
MatchAnyOfGeneric& operator=(MatchAnyOfGeneric const&) = delete;
- MatchAnyOfGeneric(MatchAnyOfGeneric&&) = default;
- MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
+ constexpr MatchAnyOfGeneric( MatchAnyOfGeneric&& ) = default;
+ constexpr MatchAnyOfGeneric& operator=(MatchAnyOfGeneric&&) = default;
- MatchAnyOfGeneric(MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND)
+ constexpr MatchAnyOfGeneric(
+ MatcherTs const&... matchers CATCH_ATTR_LIFETIMEBOUND )
: m_matchers{ {std::addressof(matchers)...} } {}
- explicit MatchAnyOfGeneric(std::array<void const*, sizeof...(MatcherTs)> matchers) : m_matchers{matchers} {}
+ constexpr explicit MatchAnyOfGeneric(
+ std::array<void const*, sizeof...( MatcherTs )> matchers ):
+ m_matchers{ matchers } {}
template<typename Arg>
- bool match(Arg&& arg) const {
+ constexpr bool match( Arg&& arg ) const {
return match_any_of<MatcherTs...>(arg, m_matchers, std::index_sequence_for<MatcherTs...>{});
}
@@ -192,7 +212,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAnyOf || GenericAnyOf` case
template<typename... MatchersRHS>
- friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...> operator || (
+ constexpr friend MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>
+ operator||(
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatchersRHS...>&& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return MatchAnyOfGeneric<MatcherTs..., MatchersRHS...>{array_cat(CATCH_MOVE(lhs.m_matchers), CATCH_MOVE(rhs.m_matchers))};
@@ -200,7 +221,8 @@ namespace Matchers {
//! Avoids type nesting for `GenericAnyOf || some matcher` case
template<typename MatcherRHS>
- friend std::enable_if_t<is_matcher_v<MatcherRHS>,
+ constexpr friend std::enable_if_t<
+ is_matcher_v<MatcherRHS>,
MatchAnyOfGeneric<MatcherTs..., MatcherRHS>> operator || (
MatchAnyOfGeneric<MatcherTs...>&& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
@@ -209,7 +231,8 @@ namespace Matchers {
//! Avoids type nesting for `some matcher || GenericAnyOf` case
template<typename MatcherLHS>
- friend std::enable_if_t<is_matcher_v<MatcherLHS>,
+ constexpr friend std::enable_if_t<
+ is_matcher_v<MatcherLHS>,
MatchAnyOfGeneric<MatcherLHS, MatcherTs...>> operator || (
MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatchAnyOfGeneric<MatcherTs...>&& rhs CATCH_ATTR_LIFETIMEBOUND) {
@@ -225,14 +248,15 @@ namespace Matchers {
public:
MatchNotOfGeneric(MatchNotOfGeneric const&) = delete;
MatchNotOfGeneric& operator=(MatchNotOfGeneric const&) = delete;
- MatchNotOfGeneric(MatchNotOfGeneric&&) = default;
- MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
+ constexpr MatchNotOfGeneric( MatchNotOfGeneric&& ) = default;
+ constexpr MatchNotOfGeneric& operator=(MatchNotOfGeneric&&) = default;
- explicit MatchNotOfGeneric(MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND)
+ constexpr explicit MatchNotOfGeneric(
+ MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND )
: m_matcher{matcher} {}
template<typename Arg>
- bool match(Arg&& arg) const {
+ constexpr bool match( Arg&& arg ) const {
return !m_matcher.match(arg);
}
@@ -241,7 +265,7 @@ namespace Matchers {
}
//! Negating negation can just unwrap and return underlying matcher
- friend MatcherT const&
+ constexpr friend MatcherT const&
operator!( MatchNotOfGeneric<MatcherT> const& matcher
CATCH_ATTR_LIFETIMEBOUND ) {
return matcher.m_matcher;
@@ -252,14 +276,18 @@ namespace Matchers {
// compose only generic matchers
template<typename MatcherLHS, typename MatcherRHS>
- std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
+ constexpr std::enable_if_t<
+ Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>,
+ Detail::MatchAllOfGeneric<MatcherLHS, MatcherRHS>>
operator&&( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
}
template<typename MatcherLHS, typename MatcherRHS>
- std::enable_if_t<Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>, Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
+ constexpr std::enable_if_t<
+ Detail::are_generic_matchers_v<MatcherLHS, MatcherRHS>,
+ Detail::MatchAnyOfGeneric<MatcherLHS, MatcherRHS>>
operator||( MatcherLHS const& lhs CATCH_ATTR_LIFETIMEBOUND,
MatcherRHS const& rhs CATCH_ATTR_LIFETIMEBOUND ) {
return { lhs, rhs };
@@ -267,7 +295,8 @@ namespace Matchers {
//! Wrap provided generic matcher in generic negator
template<typename MatcherT>
- std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>, Detail::MatchNotOfGeneric<MatcherT>>
+ constexpr std::enable_if_t<Detail::is_generic_matcher_v<MatcherT>,
+ Detail::MatchNotOfGeneric<MatcherT>>
operator!( MatcherT const& matcher CATCH_ATTR_LIFETIMEBOUND ) {
return Detail::MatchNotOfGeneric<MatcherT>{matcher};
}
diff --git a/tests/BUILD.bazel b/tests/BUILD.bazel
index 58c05205..1e192a1c 100644
--- a/tests/BUILD.bazel
+++ b/tests/BUILD.bazel
@@ -62,6 +62,7 @@ cc_test(
"SelfTest/UsageTests/Exception.tests.cpp",
"SelfTest/UsageTests/Generators.tests.cpp",
"SelfTest/UsageTests/Matchers.tests.cpp",
+ "SelfTest/UsageTests/MatchersConstexpr.tests.cpp",
"SelfTest/UsageTests/MatchersRanges.tests.cpp",
"SelfTest/UsageTests/Message.tests.cpp",
"SelfTest/UsageTests/Misc.tests.cpp",
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 3329e90f..7429734f 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -124,8 +124,9 @@ set(TEST_SOURCES
${SELF_TEST_DIR}/UsageTests/ToStringWhich.tests.cpp
${SELF_TEST_DIR}/UsageTests/Tricky.tests.cpp
${SELF_TEST_DIR}/UsageTests/VariadicMacros.tests.cpp
- ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp
${SELF_TEST_DIR}/UsageTests/Matchers.tests.cpp
+ ${SELF_TEST_DIR}/UsageTests/MatchersConstexpr.tests.cpp
+ ${SELF_TEST_DIR}/UsageTests/MatchersRanges.tests.cpp
)
set(TEST_HEADERS
diff --git a/tests/ExtraTests/CMakeLists.txt b/tests/ExtraTests/CMakeLists.txt
index 886c059e..731bcf1c 100644
--- a/tests/ExtraTests/CMakeLists.txt
+++ b/tests/ExtraTests/CMakeLists.txt
@@ -174,7 +174,7 @@ target_compile_definitions(DeferredStaticChecks PRIVATE "CATCH_CONFIG_RUNTIME_ST
add_test(NAME DeferredStaticChecks COMMAND DeferredStaticChecks -r compact)
set_tests_properties(DeferredStaticChecks
PROPERTIES
- PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed"
+ PASS_REGULAR_EXPRESSION "test cases: 1 \\| 1 failed\nassertions: 3 \\| 3 failed;test cases: 1 \\| 1 failed\nassertions: 4 \\| 4 failed"
)
add_executable(MixingClearedAndUnclearedMessages ${TESTS_DIR}/X06-MixingClearedAndUnclearedMessages.cpp)
diff --git a/tests/ExtraTests/X02-DisabledMacros.cpp b/tests/ExtraTests/X02-DisabledMacros.cpp
index 0c051acc..3449ab73 100644
--- a/tests/ExtraTests/X02-DisabledMacros.cpp
+++ b/tests/ExtraTests/X02-DisabledMacros.cpp
@@ -53,6 +53,8 @@ TEST_CASE( "Disabled Macros" ) {
REQUIRE_THAT( 1,
Catch::Matchers::Predicate( []( int ) { return false; } ) );
BENCHMARK( "Disabled benchmark" ) { REQUIRE( 1 == 2 ); };
+
+ STATIC_REQUIRE_THAT( 1, Catch::Matchers::Predicate( []( int ) { return false; } ) );
}
struct DisabledFixture {};
diff --git a/tests/ExtraTests/X05-DeferredStaticChecks.cpp b/tests/ExtraTests/X05-DeferredStaticChecks.cpp
index 8005dbcf..79ff35cc 100644
--- a/tests/ExtraTests/X05-DeferredStaticChecks.cpp
+++ b/tests/ExtraTests/X05-DeferredStaticChecks.cpp
@@ -12,10 +12,36 @@
*/
#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_templated.hpp>
+
+#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED )
+
+namespace {
+ struct MatchNoneMatcher final : public Catch::Matchers::MatcherGenericBase {
+ public:
+ template <typename Any>
+ constexpr bool match( Any&& ) const {
+ return false;
+ }
+
+ std::string describe() const override {
+ using namespace std::string_literals;
+ return "Matches anything"s;
+ }
+ };
+
+ constexpr MatchNoneMatcher MatchNone() { return MatchNoneMatcher(); }
+
+} // namespace
+
+#endif
TEST_CASE("Deferred static checks") {
STATIC_CHECK(1 == 2);
STATIC_CHECK_FALSE(1 != 2);
+#if defined(CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED)
+ STATIC_CHECK_THAT(1, MatchNone());
+#endif
// This last assertion must be executed too
CHECK(1 == 2);
}
diff --git a/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp
new file mode 100644
index 00000000..edd03519
--- /dev/null
+++ b/tests/SelfTest/UsageTests/MatchersConstexpr.tests.cpp
@@ -0,0 +1,52 @@
+
+// Copyright Catch2 Authors
+// Distributed under the Boost Software License, Version 1.0.
+// (See accompanying file LICENSE.txt or copy at
+// https://www.boost.org/LICENSE_1_0.txt)
+
+// SPDX-License-Identifier: BSL-1.0
+
+#include <catch2/catch_test_macros.hpp>
+#include <catch2/matchers/catch_matchers_templated.hpp>
+
+#if defined( CATCH_INTERNAL_CONSTEXPR_MATCHERS_ENABLED )
+
+namespace {
+ struct MatchAllMatcher final : public Catch::Matchers::MatcherGenericBase {
+ public:
+ template <typename Any>
+ constexpr bool match( Any&& ) const {
+ return true;
+ }
+
+ std::string describe() const override {
+ using namespace std::string_literals;
+ return "Matches anything"s;
+ }
+ };
+
+ constexpr MatchAllMatcher MatchAll() { return MatchAllMatcher(); }
+
+} // namespace
+
+TEST_CASE( "Constexpr support for matchers", "[constexpr][matchers][approvals]" ) {
+ STATIC_REQUIRE( MatchAll().match( 1 ) );
+ STATIC_REQUIRE_THAT( 1, MatchAll() );
+}
+
+// Combining matchers needs C++26 and P2738, so they are in separate preprocessor block
+# if __cpp_constexpr >= 202306L
+
+TEST_CASE("Constexpr support for combining matchers",
+ "[constexpr][matchers][approvals]") {
+ STATIC_REQUIRE( ( MatchAll() && MatchAll() ).match( 1 ) );
+ STATIC_REQUIRE( ( MatchAll() || MatchAll() ).match( 1 ) );
+ STATIC_REQUIRE( ( !!MatchAll() ).match( 1 ) );
+ STATIC_REQUIRE_THAT( 1, MatchAll() && MatchAll() );
+ STATIC_REQUIRE_THAT( 1, MatchAll() || MatchAll() );
+ STATIC_REQUIRE_THAT( 1, !!MatchAll() );
+}
+
+#endif // __cpp_constexpr >= 202306L
+
+#endif
diff --git a/tests/meson.build b/tests/meson.build
index 58302b7a..0e44eaab 100644
--- a/tests/meson.build
+++ b/tests/meson.build
@@ -49,6 +49,7 @@ self_test_sources = files(
'SelfTest/UsageTests/Exception.tests.cpp',
'SelfTest/UsageTests/Generators.tests.cpp',
'SelfTest/UsageTests/Matchers.tests.cpp',
+ 'SelfTest/UsageTests/MatchersConstexpr.tests.cpp',
'SelfTest/UsageTests/MatchersRanges.tests.cpp',
'SelfTest/UsageTests/Message.tests.cpp',
'SelfTest/UsageTests/Misc.tests.cpp',