Fix assertion for const instance of std::ordering

The issue was that `capture_by_value` was meant to be specialized
by plain type, e.g. `capture_by_value<std::weak_ordering>`, but
the TMP in Decomposer did not properly throw away `const` (and
`volatile`) qualifiers, before taking the value of `capture_by_value`.
This commit is contained in:
Martin Hořeňovský 2024-04-08 11:02:22 +02:00
parent 0a6a2ce887
commit 71b11c4e33
No known key found for this signature in database
GPG Key ID: DE48307B8B0D381A
2 changed files with 56 additions and 16 deletions

View File

@ -125,6 +125,12 @@
namespace Catch { namespace Catch {
namespace Detail {
// This was added in C++20, but we require only C++14 for now.
template <typename T>
using RemoveCVRef_t = std::remove_cv_t<std::remove_reference_t<T>>;
}
// Note: There is nothing that stops us from extending this, // Note: There is nothing that stops us from extending this,
// e.g. to `std::is_scalar`, but the more encompassing // e.g. to `std::is_scalar`, but the more encompassing
// traits are usually also more expensive. For now we // traits are usually also more expensive. For now we
@ -279,7 +285,7 @@ namespace Catch {
-> std::enable_if_t< \ -> std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
Detail::negation<capture_by_value< \ Detail::negation<capture_by_value< \
std::remove_reference_t<RhsT>>>>::value, \ Detail::RemoveCVRef_t<RhsT>>>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \ BinaryExpr<LhsT, RhsT const&>> { \
return { \ return { \
static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
@ -333,7 +339,7 @@ namespace Catch {
-> std::enable_if_t< \ -> std::enable_if_t< \
Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \ Detail::conjunction<Detail::is_##id##_comparable<LhsT, RhsT>, \
Detail::negation<capture_by_value< \ Detail::negation<capture_by_value< \
std::remove_reference_t<RhsT>>>>::value, \ Detail::RemoveCVRef_t<RhsT>>>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \ BinaryExpr<LhsT, RhsT const&>> { \
return { \ return { \
static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
@ -383,7 +389,7 @@ namespace Catch {
template <typename RhsT> \ template <typename RhsT> \
constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \ constexpr friend auto operator op( ExprLhs&& lhs, RhsT&& rhs ) \
-> std::enable_if_t< \ -> std::enable_if_t< \
!capture_by_value<std::remove_reference_t<RhsT>>::value, \ !capture_by_value<Detail::RemoveCVRef_t<RhsT>>::value, \
BinaryExpr<LhsT, RhsT const&>> { \ BinaryExpr<LhsT, RhsT const&>> { \
return { \ return { \
static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \ static_cast<bool>( lhs.m_lhs op rhs ), lhs.m_lhs, #op##_sr, rhs }; \
@ -423,8 +429,7 @@ namespace Catch {
struct Decomposer { struct Decomposer {
template <typename T, template <typename T,
std::enable_if_t< std::enable_if_t<!capture_by_value<Detail::RemoveCVRef_t<T>>::value,
!capture_by_value<std::remove_reference_t<T>>::value,
int> = 0> int> = 0>
constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> { constexpr friend auto operator <= ( Decomposer &&, T && lhs ) -> ExprLhs<T const&> {
return ExprLhs<const T&>{ lhs }; return ExprLhs<const T&>{ lhs };

View File

@ -357,6 +357,12 @@ namespace {
constexpr friend bool operator op( ZeroLiteralConsteval, \ constexpr friend bool operator op( ZeroLiteralConsteval, \
TypeWithConstevalLit0Comparison ) { \ TypeWithConstevalLit0Comparison ) { \
return false; \ return false; \
} \
/* std::orderings only have these for ==, but we add them for all \
operators so we can test all overloads for decomposer */ \
constexpr friend bool operator op( TypeWithConstevalLit0Comparison, \
TypeWithConstevalLit0Comparison ) { \
return true; \
} }
DEFINE_COMP_OP( < ) DEFINE_COMP_OP( < )
@ -394,6 +400,21 @@ TEST_CASE( "#2555 - types that can only be compared with 0 literal implemented a
REQUIRE_FALSE( 0 != TypeWithConstevalLit0Comparison{} ); REQUIRE_FALSE( 0 != TypeWithConstevalLit0Comparison{} );
} }
// We check all comparison ops to test, even though orderings, the primary
// motivation for this functionality, only have self-comparison (and thus
// have the ambiguity issue) for `==` and `!=`.
TEST_CASE( "Comparing const instances of type registered with capture_by_value",
"[regression][approvals][compilation]" ) {
auto const const_Lit0Type_1 = TypeWithLit0Comparisons{};
auto const const_Lit0Type_2 = TypeWithLit0Comparisons{};
REQUIRE( const_Lit0Type_1 == const_Lit0Type_2 );
REQUIRE( const_Lit0Type_1 <= const_Lit0Type_2 );
REQUIRE( const_Lit0Type_1 < const_Lit0Type_2 );
REQUIRE( const_Lit0Type_1 >= const_Lit0Type_2 );
REQUIRE( const_Lit0Type_1 > const_Lit0Type_2 );
REQUIRE_FALSE( const_Lit0Type_1 != const_Lit0Type_2 );
}
#endif // C++20 consteval #endif // C++20 consteval
@ -420,3 +441,17 @@ TEST_CASE("#2571 - tests compile types that have multiple implicit constructors
REQUIRE( mic1 > mic2 ); REQUIRE( mic1 > mic2 );
REQUIRE( mic1 >= mic2 ); REQUIRE( mic1 >= mic2 );
} }
#if defined( CATCH_CONFIG_CPP20_COMPARE_OVERLOADS )
// This test does not test all the related codepaths, but it is the original
// reproducer
TEST_CASE( "Comparing const std::weak_ordering instances must compile",
"[compilation][approvals][regression]" ) {
auto const const_ordering_1 = std::weak_ordering::less;
auto const const_ordering_2 = std::weak_ordering::less;
auto plain_ordering_1 = std::weak_ordering::less;
REQUIRE( const_ordering_1 == plain_ordering_1 );
REQUIRE( const_ordering_1 == const_ordering_2 );
REQUIRE( plain_ordering_1 == const_ordering_1 );
}
#endif