2022-01-29 00:03:43 +01:00
|
|
|
|
|
|
|
// Copyright Catch2 Authors
|
|
|
|
// Distributed under the Boost Software License, Version 1.0.
|
2022-10-28 11:22:53 +02:00
|
|
|
// (See accompanying file LICENSE.txt or copy at
|
2022-01-29 00:03:43 +01:00
|
|
|
// https://www.boost.org/LICENSE_1_0.txt)
|
|
|
|
|
|
|
|
// SPDX-License-Identifier: BSL-1.0
|
2017-02-07 13:32:48 +01:00
|
|
|
|
2022-11-01 15:01:43 +01:00
|
|
|
#include <helpers/type_with_lit_0_comparisons.hpp>
|
|
|
|
|
2018-10-13 21:29:59 +02:00
|
|
|
#include <type_traits>
|
|
|
|
|
2018-10-13 14:43:58 +02:00
|
|
|
// Setup for #1403 -- look for global overloads of operator << for classes
|
|
|
|
// in a different namespace.
|
|
|
|
#include <ostream>
|
|
|
|
|
|
|
|
namespace foo {
|
|
|
|
struct helper_1403 {
|
|
|
|
bool operator==(helper_1403) const { return true; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
2019-02-23 21:06:16 +01:00
|
|
|
namespace bar {
|
|
|
|
template <typename... Ts>
|
|
|
|
struct TypeList {};
|
|
|
|
}
|
|
|
|
|
2018-10-13 14:43:58 +02:00
|
|
|
#ifdef __GNUC__
|
|
|
|
#pragma GCC diagnostic ignored "-Wmissing-declarations"
|
|
|
|
#endif
|
2022-05-02 12:57:46 +02:00
|
|
|
static std::ostream& operator<<(std::ostream& out, foo::helper_1403 const&) {
|
2018-10-13 14:43:58 +02:00
|
|
|
return out << "[1403 helper]";
|
|
|
|
}
|
|
|
|
///////////////////////////////
|
2018-10-13 21:29:59 +02:00
|
|
|
|
2020-01-20 23:24:04 +01:00
|
|
|
#include <catch2/catch_test_macros.hpp>
|
2020-03-01 19:59:18 +01:00
|
|
|
#include <catch2/matchers/catch_matchers_string.hpp>
|
2017-02-07 13:32:48 +01:00
|
|
|
|
2018-04-06 11:39:40 +02:00
|
|
|
#include <cstring>
|
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
// Comparison operators can return non-booleans.
|
|
|
|
// This is unusual, but should be supported.
|
|
|
|
struct logic_t {
|
|
|
|
logic_t operator< (logic_t) const { return {}; }
|
|
|
|
logic_t operator<=(logic_t) const { return {}; }
|
|
|
|
logic_t operator> (logic_t) const { return {}; }
|
|
|
|
logic_t operator>=(logic_t) const { return {}; }
|
|
|
|
logic_t operator==(logic_t) const { return {}; }
|
|
|
|
logic_t operator!=(logic_t) const { return {}; }
|
|
|
|
explicit operator bool() const { return true; }
|
|
|
|
};
|
|
|
|
|
|
|
|
|
2022-05-02 12:57:46 +02:00
|
|
|
static void throws_int(bool b) {
|
2021-06-20 16:25:57 +02:00
|
|
|
if (b) {
|
|
|
|
throw 1;
|
2017-11-15 08:48:21 +01:00
|
|
|
}
|
2021-06-20 16:25:57 +02:00
|
|
|
}
|
2017-03-23 21:11:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
template<typename T>
|
|
|
|
bool templated_tests(T t) {
|
|
|
|
int a = 3;
|
|
|
|
REQUIRE(a == t);
|
|
|
|
CHECK(a == t);
|
|
|
|
REQUIRE_THROWS(throws_int(true));
|
|
|
|
CHECK_THROWS_AS(throws_int(true), int);
|
|
|
|
REQUIRE_NOTHROW(throws_int(false));
|
|
|
|
REQUIRE_THAT("aaa", Catch::Matchers::EndsWith("aaa"));
|
|
|
|
return true;
|
|
|
|
}
|
2017-11-15 08:48:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
struct A {};
|
2017-11-15 08:48:21 +01:00
|
|
|
|
2022-05-02 12:57:46 +02:00
|
|
|
static std::ostream &operator<<(std::ostream &o, const A &) { return o << 0; }
|
2017-03-23 21:11:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
struct B : private A {
|
|
|
|
bool operator==(int) const { return true; }
|
|
|
|
};
|
2017-03-23 21:11:21 +01:00
|
|
|
|
2017-04-25 15:57:58 +02:00
|
|
|
#ifdef __clang__
|
|
|
|
#pragma clang diagnostic push
|
|
|
|
#pragma clang diagnostic ignored "-Wunused-function"
|
|
|
|
#endif
|
2017-06-05 16:59:31 +02:00
|
|
|
#ifdef __GNUC__
|
|
|
|
// Note that because -~GCC~-, this warning cannot be silenced temporarily, by pushing diagnostic stack...
|
|
|
|
// Luckily it is firing in test files and thus can be silenced for the whole file, without losing much.
|
|
|
|
#pragma GCC diagnostic ignored "-Wunused-function"
|
|
|
|
#endif
|
2017-04-25 15:57:58 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
B f();
|
2017-04-05 09:53:10 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
std::ostream g();
|
2017-04-05 09:53:10 +02:00
|
|
|
|
2017-04-25 15:57:58 +02:00
|
|
|
#ifdef __clang__
|
|
|
|
#pragma clang diagnostic pop
|
|
|
|
#endif
|
2017-04-05 09:53:10 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
template <typename, typename>
|
|
|
|
struct Fixture_1245 {};
|
2018-04-10 04:41:44 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
// This is a minimal example for an issue we have found in 1.7.0
|
|
|
|
struct dummy_809 {
|
|
|
|
int i;
|
|
|
|
};
|
2017-11-15 08:48:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
template<typename T>
|
|
|
|
bool operator==(const T& val, dummy_809 f) {
|
|
|
|
return val == f.i;
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("#809") {
|
|
|
|
dummy_809 f;
|
|
|
|
f.i = 42;
|
|
|
|
REQUIRE(42 == f);
|
|
|
|
}
|
2017-11-15 08:48:21 +01:00
|
|
|
|
|
|
|
|
|
|
|
// ------------------------------------------------------------------
|
|
|
|
// Changes to REQUIRE_THROWS_AS made it stop working in a template in
|
|
|
|
// an unfixable way (as long as C++03 compatibility is being kept).
|
|
|
|
// To prevent these from happening in the future, this needs to compile
|
|
|
|
|
|
|
|
TEST_CASE("#833") {
|
|
|
|
REQUIRE(templated_tests<int>(3));
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Test containing example where original stream insertable check breaks compilation
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("#872") {
|
|
|
|
A dummy;
|
|
|
|
CAPTURE(dummy);
|
|
|
|
B x;
|
|
|
|
REQUIRE (x == 4);
|
|
|
|
}
|
2017-11-15 08:48:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("#1027: Bitfields can be captured") {
|
|
|
|
struct Y {
|
|
|
|
uint32_t v : 1;
|
|
|
|
};
|
|
|
|
Y y{ 0 };
|
|
|
|
REQUIRE(y.v == 0);
|
|
|
|
REQUIRE(0 == y.v);
|
|
|
|
}
|
2017-11-15 08:48:21 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
// Comparison operators can return non-booleans.
|
|
|
|
// This is unusual, but should be supported.
|
|
|
|
TEST_CASE("#1147") {
|
|
|
|
logic_t t1, t2;
|
|
|
|
REQUIRE(t1 == t2);
|
|
|
|
REQUIRE(t1 != t2);
|
|
|
|
REQUIRE(t1 < t2);
|
|
|
|
REQUIRE(t1 > t2);
|
|
|
|
REQUIRE(t1 <= t2);
|
|
|
|
REQUIRE(t1 >= t2);
|
|
|
|
}
|
2018-01-12 15:43:56 +01:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
// unsigned array
|
|
|
|
TEST_CASE("#1238") {
|
|
|
|
unsigned char uarr[] = "123";
|
|
|
|
CAPTURE(uarr);
|
|
|
|
signed char sarr[] = "456";
|
|
|
|
CAPTURE(sarr);
|
2018-04-06 11:39:40 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
REQUIRE(std::memcmp(uarr, "123", sizeof(uarr)) == 0);
|
|
|
|
REQUIRE(std::memcmp(sarr, "456", sizeof(sarr)) == 0);
|
|
|
|
}
|
2018-04-06 11:39:40 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE_METHOD((Fixture_1245<int, int>), "#1245", "[compilation]") {
|
|
|
|
SUCCEED();
|
|
|
|
}
|
2018-10-13 21:29:59 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("#1403", "[compilation]") {
|
|
|
|
::foo::helper_1403 h1, h2;
|
|
|
|
REQUIRE(h1 == h2);
|
|
|
|
}
|
2018-10-13 21:29:59 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("Optionally static assertions", "[compilation]") {
|
|
|
|
STATIC_REQUIRE( std::is_void<void>::value );
|
|
|
|
STATIC_REQUIRE_FALSE( std::is_void<int>::value );
|
2021-11-15 00:28:27 +01:00
|
|
|
STATIC_CHECK( std::is_void<void>::value );
|
|
|
|
STATIC_CHECK_FALSE( std::is_void<int>::value );
|
2021-06-20 16:25:57 +02:00
|
|
|
}
|
2018-10-13 21:29:59 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("#1548", "[compilation]") {
|
|
|
|
using namespace bar;
|
|
|
|
REQUIRE(std::is_same<TypeList<int>, TypeList<int>>::value);
|
|
|
|
}
|
2019-02-23 21:06:16 +01:00
|
|
|
|
2019-08-04 13:50:25 +02:00
|
|
|
// #925
|
|
|
|
using signal_t = void (*) (void*);
|
|
|
|
|
|
|
|
struct TestClass {
|
|
|
|
signal_t testMethod_uponComplete_arg = nullptr;
|
|
|
|
};
|
2018-10-13 14:43:58 +02:00
|
|
|
|
2019-08-04 13:50:25 +02:00
|
|
|
namespace utility {
|
|
|
|
inline static void synchronizing_callback( void * ) { }
|
|
|
|
}
|
|
|
|
|
2020-03-18 20:59:25 +01:00
|
|
|
#if defined (_MSC_VER)
|
|
|
|
#pragma warning(push)
|
|
|
|
// The function pointer comparison below triggers warning because of
|
|
|
|
// calling conventions
|
|
|
|
#pragma warning(disable:4244)
|
|
|
|
#endif
|
2019-08-04 13:50:25 +02:00
|
|
|
TEST_CASE("#925: comparing function pointer to function address failed to compile", "[!nonportable]" ) {
|
|
|
|
TestClass test;
|
|
|
|
REQUIRE(utility::synchronizing_callback != test.testMethod_uponComplete_arg);
|
|
|
|
}
|
2020-03-18 20:59:25 +01:00
|
|
|
#if defined (_MSC_VER)
|
|
|
|
#pragma warning(pop)
|
|
|
|
#endif
|
2019-08-04 13:50:25 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE( "#1319: Sections can have description (even if it is not saved",
|
|
|
|
"[compilation]" ) {
|
|
|
|
SECTION( "SectionName", "This is a long form section description" ) {
|
|
|
|
SUCCEED();
|
2020-03-19 13:32:45 +01:00
|
|
|
}
|
2021-06-20 16:25:57 +02:00
|
|
|
}
|
2019-08-04 13:50:25 +02:00
|
|
|
|
2021-06-20 16:25:57 +02:00
|
|
|
TEST_CASE("Lambdas in assertions") {
|
|
|
|
REQUIRE([]() { return true; }());
|
|
|
|
}
|
2018-10-13 14:43:58 +02:00
|
|
|
|
2020-04-21 11:00:08 +02:00
|
|
|
namespace {
|
|
|
|
struct HasBitOperators {
|
|
|
|
int value;
|
|
|
|
|
|
|
|
friend HasBitOperators operator| (HasBitOperators lhs, HasBitOperators rhs) {
|
|
|
|
return { lhs.value | rhs.value };
|
|
|
|
}
|
|
|
|
friend HasBitOperators operator& (HasBitOperators lhs, HasBitOperators rhs) {
|
|
|
|
return { lhs.value & rhs.value };
|
|
|
|
}
|
2020-04-21 19:27:12 +02:00
|
|
|
friend HasBitOperators operator^ (HasBitOperators lhs, HasBitOperators rhs) {
|
|
|
|
return { lhs.value ^ rhs.value };
|
|
|
|
}
|
2020-04-21 11:00:08 +02:00
|
|
|
explicit operator bool() const {
|
|
|
|
return !!value;
|
|
|
|
}
|
|
|
|
|
|
|
|
friend std::ostream& operator<<(std::ostream& out, HasBitOperators val) {
|
|
|
|
out << "Val: " << val.value;
|
|
|
|
return out;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("Assertion macros support bit operators and bool conversions", "[compilation][bitops]") {
|
|
|
|
HasBitOperators lhs{ 1 }, rhs{ 2 };
|
|
|
|
REQUIRE(lhs | rhs);
|
|
|
|
REQUIRE_FALSE(lhs & rhs);
|
|
|
|
REQUIRE(HasBitOperators{ 1 } & HasBitOperators{ 1 });
|
2020-04-21 19:27:12 +02:00
|
|
|
REQUIRE(lhs ^ rhs);
|
|
|
|
REQUIRE_FALSE(lhs ^ lhs);
|
2020-04-21 11:00:08 +02:00
|
|
|
}
|
|
|
|
|
2021-06-07 15:17:46 +02:00
|
|
|
namespace {
|
|
|
|
struct ImmovableType {
|
|
|
|
ImmovableType() = default;
|
|
|
|
|
|
|
|
ImmovableType(ImmovableType const&) = delete;
|
|
|
|
ImmovableType& operator=(ImmovableType const&) = delete;
|
|
|
|
ImmovableType(ImmovableType&&) = delete;
|
|
|
|
ImmovableType& operator=(ImmovableType&&) = delete;
|
|
|
|
|
|
|
|
friend bool operator==(ImmovableType const&, ImmovableType const&) {
|
|
|
|
return true;
|
|
|
|
}
|
|
|
|
};
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("Immovable types are supported in basic assertions", "[compilation][.approvals]") {
|
|
|
|
REQUIRE(ImmovableType{} == ImmovableType{});
|
|
|
|
}
|
2020-12-20 00:36:19 +01:00
|
|
|
|
|
|
|
namespace adl {
|
|
|
|
|
|
|
|
struct always_true {
|
|
|
|
explicit operator bool() const { return true; }
|
|
|
|
};
|
|
|
|
|
|
|
|
#define COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(op) \
|
|
|
|
template <class T, class U> \
|
|
|
|
auto operator op (T&&, U&&) { \
|
|
|
|
return always_true{}; \
|
|
|
|
}
|
|
|
|
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(==)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(!=)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(<)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(>)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(<=)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(>=)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(|)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(&)
|
|
|
|
COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR(^)
|
|
|
|
|
|
|
|
#undef COMPILATION_TEST_DEFINE_UNIVERSAL_OPERATOR
|
|
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE("ADL universal operators don't hijack expression deconstruction", "[compilation][.approvals]") {
|
|
|
|
REQUIRE(adl::always_true{});
|
|
|
|
REQUIRE(0 == adl::always_true{});
|
|
|
|
REQUIRE(0 != adl::always_true{});
|
|
|
|
REQUIRE(0 < adl::always_true{});
|
|
|
|
REQUIRE(0 > adl::always_true{});
|
|
|
|
REQUIRE(0 <= adl::always_true{});
|
|
|
|
REQUIRE(0 >= adl::always_true{});
|
|
|
|
REQUIRE(0 | adl::always_true{});
|
|
|
|
REQUIRE(0 & adl::always_true{});
|
|
|
|
REQUIRE(0 ^ adl::always_true{});
|
|
|
|
}
|
2022-11-01 15:01:43 +01:00
|
|
|
|
Support literal-zero detectors using consteval int constructors
This was originally motivated by `REQUIRE((a <=> b) == 0)` no
longer compiling using MSVC. After some investigation, I found
that they changed their implementation of the zero literal
detector from the previous pointer-constructor with deleted
other constructors, into one that uses `consteval` constructor
from int.
This breaks the previous detection logic, because now
`is_foo_comparable<std::strong_ordering, int>` is true, but
actually trying to compare them is a compile-time error...
The solution was to make the decomposition `constexpr` and rely
on a late C++20 DR that makes it so that `consteval` propagates
up through the callstack of `constexpr` functions, until it either
runs out of `constexpr` functions, or succeeds.
However, the default handling of types in decomposition is to
take a reference to them. This reference never becomes dangling,
but because the constexpr evaluation engine cannot prove this,
decomposition paths taking references to objects cannot be
actually evaluated at compilation time. Thankfully we already
did have a value-oriented decomposition path for arithmetic types
(as these are common linkage-less types), so we could just
explicitly spell out the `std::foo_ordering` types as also being
supposed to be decomposed by-value.
Two more fun facts about these changes
1) The original motivation of the MSVC change was to avoid
trigering a `Wzero-as-null-pointer-constant` warning. I still
do not believe this was a good decision.
2) Current latest version of MSVC does not actually implement the
aforementioned C++20 DR, so even with this commit, MSVC cannot
compile `REQUIRE((a <=> b) == 0)`.
2024-02-08 22:21:18 +01:00
|
|
|
TEST_CASE( "#2555 - types that can only be compared with 0 literal implemented as pointer conversion are supported",
|
|
|
|
"[compilation][approvals]" ) {
|
2022-11-01 15:01:43 +01:00
|
|
|
REQUIRE( TypeWithLit0Comparisons{} < 0 );
|
|
|
|
REQUIRE_FALSE( 0 < TypeWithLit0Comparisons{} );
|
|
|
|
REQUIRE( TypeWithLit0Comparisons{} <= 0 );
|
Support literal-zero detectors using consteval int constructors
This was originally motivated by `REQUIRE((a <=> b) == 0)` no
longer compiling using MSVC. After some investigation, I found
that they changed their implementation of the zero literal
detector from the previous pointer-constructor with deleted
other constructors, into one that uses `consteval` constructor
from int.
This breaks the previous detection logic, because now
`is_foo_comparable<std::strong_ordering, int>` is true, but
actually trying to compare them is a compile-time error...
The solution was to make the decomposition `constexpr` and rely
on a late C++20 DR that makes it so that `consteval` propagates
up through the callstack of `constexpr` functions, until it either
runs out of `constexpr` functions, or succeeds.
However, the default handling of types in decomposition is to
take a reference to them. This reference never becomes dangling,
but because the constexpr evaluation engine cannot prove this,
decomposition paths taking references to objects cannot be
actually evaluated at compilation time. Thankfully we already
did have a value-oriented decomposition path for arithmetic types
(as these are common linkage-less types), so we could just
explicitly spell out the `std::foo_ordering` types as also being
supposed to be decomposed by-value.
Two more fun facts about these changes
1) The original motivation of the MSVC change was to avoid
trigering a `Wzero-as-null-pointer-constant` warning. I still
do not believe this was a good decision.
2) Current latest version of MSVC does not actually implement the
aforementioned C++20 DR, so even with this commit, MSVC cannot
compile `REQUIRE((a <=> b) == 0)`.
2024-02-08 22:21:18 +01:00
|
|
|
REQUIRE_FALSE( 0 <= TypeWithLit0Comparisons{} );
|
2022-11-01 15:01:43 +01:00
|
|
|
|
|
|
|
REQUIRE( TypeWithLit0Comparisons{} > 0 );
|
|
|
|
REQUIRE_FALSE( 0 > TypeWithLit0Comparisons{} );
|
|
|
|
REQUIRE( TypeWithLit0Comparisons{} >= 0 );
|
|
|
|
REQUIRE_FALSE( 0 >= TypeWithLit0Comparisons{} );
|
|
|
|
|
|
|
|
REQUIRE( TypeWithLit0Comparisons{} == 0 );
|
|
|
|
REQUIRE_FALSE( 0 == TypeWithLit0Comparisons{} );
|
|
|
|
REQUIRE( TypeWithLit0Comparisons{} != 0 );
|
|
|
|
REQUIRE_FALSE( 0 != TypeWithLit0Comparisons{} );
|
|
|
|
}
|
Move SFINAE in decomposer into return type
This is needed so that we can use conjunction and other logical
type traits to workaround issue with older GCC versions (8 and
below), when they run into types that have ambiguous constructor
from `0`, see e.g. #2571.
However, using conjunction and friends in the SFINAE constraint
in the template parameter breaks for C++20 and up, due to the new
comparison operator rewriting rules. With C++20, when the compiler
see `a == b`, it also tries `b == a` and collects overload set
for both of these expressions.
In Catch2, this means that e.g. `REQUIRE( 1 == 2 )` would lead
the compiler to check overloads for both `ExprLhs<int> == int`
and `int == ExprLhs<int>`. Since the overload set and SFINAE
constraints assume that `ExprLhs<T>` is always on the left side,
when the compiler tries to resolve the template parameters, all
hell breaks loose and the compilation fails.
By moving the SFINAE constraints to the return type, the compiler
can discard the switched expression without having to resolve
the complex SFINAE constraints, and thus everything works the
way it is supposed to.
Fixes #2571
2022-12-08 16:31:55 +01:00
|
|
|
|
Support literal-zero detectors using consteval int constructors
This was originally motivated by `REQUIRE((a <=> b) == 0)` no
longer compiling using MSVC. After some investigation, I found
that they changed their implementation of the zero literal
detector from the previous pointer-constructor with deleted
other constructors, into one that uses `consteval` constructor
from int.
This breaks the previous detection logic, because now
`is_foo_comparable<std::strong_ordering, int>` is true, but
actually trying to compare them is a compile-time error...
The solution was to make the decomposition `constexpr` and rely
on a late C++20 DR that makes it so that `consteval` propagates
up through the callstack of `constexpr` functions, until it either
runs out of `constexpr` functions, or succeeds.
However, the default handling of types in decomposition is to
take a reference to them. This reference never becomes dangling,
but because the constexpr evaluation engine cannot prove this,
decomposition paths taking references to objects cannot be
actually evaluated at compilation time. Thankfully we already
did have a value-oriented decomposition path for arithmetic types
(as these are common linkage-less types), so we could just
explicitly spell out the `std::foo_ordering` types as also being
supposed to be decomposed by-value.
Two more fun facts about these changes
1) The original motivation of the MSVC change was to avoid
trigering a `Wzero-as-null-pointer-constant` warning. I still
do not believe this was a good decision.
2) Current latest version of MSVC does not actually implement the
aforementioned C++20 DR, so even with this commit, MSVC cannot
compile `REQUIRE((a <=> b) == 0)`.
2024-02-08 22:21:18 +01:00
|
|
|
// These tests require `consteval` to propagate through `constexpr` calls
|
|
|
|
// which is a late DR against C++20.
|
|
|
|
#if defined( CATCH_CPP20_OR_GREATER ) && defined( __cpp_consteval ) && \
|
|
|
|
__cpp_consteval >= 202211L
|
|
|
|
// Can't have internal linkage to avoid warnings
|
|
|
|
void ZeroLiteralErrorFunc();
|
|
|
|
namespace {
|
|
|
|
struct ZeroLiteralConsteval {
|
|
|
|
template <class T, std::enable_if_t<std::is_same_v<T, int>, int> = 0>
|
|
|
|
consteval ZeroLiteralConsteval( T zero ) noexcept {
|
|
|
|
if ( zero != 0 ) { ZeroLiteralErrorFunc(); }
|
|
|
|
}
|
|
|
|
};
|
|
|
|
|
|
|
|
// Should only be constructible from literal 0. Uses the propagating
|
|
|
|
// consteval constructor trick (currently used by MSVC, might be used
|
|
|
|
// by libc++ in the future as well).
|
|
|
|
struct TypeWithConstevalLit0Comparison {
|
|
|
|
# define DEFINE_COMP_OP( op ) \
|
|
|
|
constexpr friend bool operator op( TypeWithConstevalLit0Comparison, \
|
|
|
|
ZeroLiteralConsteval ) { \
|
|
|
|
return true; \
|
|
|
|
} \
|
|
|
|
constexpr friend bool operator op( ZeroLiteralConsteval, \
|
|
|
|
TypeWithConstevalLit0Comparison ) { \
|
|
|
|
return false; \
|
|
|
|
}
|
|
|
|
|
|
|
|
DEFINE_COMP_OP( < )
|
|
|
|
DEFINE_COMP_OP( <= )
|
|
|
|
DEFINE_COMP_OP( > )
|
|
|
|
DEFINE_COMP_OP( >= )
|
|
|
|
DEFINE_COMP_OP( == )
|
|
|
|
DEFINE_COMP_OP( != )
|
|
|
|
|
|
|
|
#undef DEFINE_COMP_OP
|
|
|
|
};
|
|
|
|
|
|
|
|
} // namespace
|
|
|
|
|
|
|
|
namespace Catch {
|
|
|
|
template <>
|
|
|
|
struct capture_by_value<TypeWithConstevalLit0Comparison> : std::true_type {};
|
|
|
|
}
|
|
|
|
|
|
|
|
TEST_CASE( "#2555 - types that can only be compared with 0 literal implemented as consteval check are supported",
|
|
|
|
"[compilation][approvals]" ) {
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} < 0 );
|
|
|
|
REQUIRE_FALSE( 0 < TypeWithConstevalLit0Comparison{} );
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} <= 0 );
|
|
|
|
REQUIRE_FALSE( 0 <= TypeWithConstevalLit0Comparison{} );
|
|
|
|
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} > 0 );
|
|
|
|
REQUIRE_FALSE( 0 > TypeWithConstevalLit0Comparison{} );
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} >= 0 );
|
|
|
|
REQUIRE_FALSE( 0 >= TypeWithConstevalLit0Comparison{} );
|
|
|
|
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} == 0 );
|
|
|
|
REQUIRE_FALSE( 0 == TypeWithConstevalLit0Comparison{} );
|
|
|
|
REQUIRE( TypeWithConstevalLit0Comparison{} != 0 );
|
|
|
|
REQUIRE_FALSE( 0 != TypeWithConstevalLit0Comparison{} );
|
|
|
|
}
|
|
|
|
|
|
|
|
#endif // C++20 consteval
|
|
|
|
|
|
|
|
|
Move SFINAE in decomposer into return type
This is needed so that we can use conjunction and other logical
type traits to workaround issue with older GCC versions (8 and
below), when they run into types that have ambiguous constructor
from `0`, see e.g. #2571.
However, using conjunction and friends in the SFINAE constraint
in the template parameter breaks for C++20 and up, due to the new
comparison operator rewriting rules. With C++20, when the compiler
see `a == b`, it also tries `b == a` and collects overload set
for both of these expressions.
In Catch2, this means that e.g. `REQUIRE( 1 == 2 )` would lead
the compiler to check overloads for both `ExprLhs<int> == int`
and `int == ExprLhs<int>`. Since the overload set and SFINAE
constraints assume that `ExprLhs<T>` is always on the left side,
when the compiler tries to resolve the template parameters, all
hell breaks loose and the compilation fails.
By moving the SFINAE constraints to the return type, the compiler
can discard the switched expression without having to resolve
the complex SFINAE constraints, and thus everything works the
way it is supposed to.
Fixes #2571
2022-12-08 16:31:55 +01:00
|
|
|
namespace {
|
|
|
|
struct MultipleImplicitConstructors {
|
|
|
|
MultipleImplicitConstructors( double ) {}
|
|
|
|
MultipleImplicitConstructors( int64_t ) {}
|
|
|
|
bool operator==( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
bool operator!=( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
bool operator<( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
bool operator<=( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
bool operator>( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
bool operator>=( MultipleImplicitConstructors ) const { return true; }
|
|
|
|
};
|
|
|
|
}
|
|
|
|
TEST_CASE("#2571 - tests compile types that have multiple implicit constructors from lit 0",
|
|
|
|
"[compilation][approvals]") {
|
|
|
|
MultipleImplicitConstructors mic1( 0.0 );
|
|
|
|
MultipleImplicitConstructors mic2( 0.0 );
|
|
|
|
REQUIRE( mic1 == mic2 );
|
|
|
|
REQUIRE( mic1 != mic2 );
|
|
|
|
REQUIRE( mic1 < mic2 );
|
|
|
|
REQUIRE( mic1 <= mic2 );
|
|
|
|
REQUIRE( mic1 > mic2 );
|
|
|
|
REQUIRE( mic1 >= mic2 );
|
|
|
|
}
|