Richard Smith 4a4e90a823 [c++20] Compute exception specifications for defaulted comparisons.
This requires us to essentially fully form the body of the defaulted
comparison, but from an unevaluated context. Naively this would require
generating the function definition twice; instead, we ensure that the
function body is implicitly defined before performing the check, and
walk the actual body where possible.
2019-12-15 22:02:31 -08:00

227 lines
6.6 KiB
C++

// RUN: %clang_cc1 -std=c++2a -verify %s
// RUN: %clang_cc1 -std=c++2a -verify %s -DDEFINE_FIRST
// As modified by P2002R0:
// The exception specification for a comparison operator function (12.6.2)
// without a noexcept-specifier that is defaulted on its first declaration is
// potentially-throwing if and only if any expression in the implicit
// definition is potentially-throwing.
#define CAT2(a, b) a ## b
#define CAT(a, b) CAT2(a, b)
#ifdef DEFINE_FIRST
#define DEF(x) auto CAT(a, __LINE__) = x
#else
#define DEF(x)
#endif
namespace std {
struct strong_ordering {
int n;
static const strong_ordering equal, less, greater;
};
constexpr strong_ordering strong_ordering::equal{0},
strong_ordering::less{-1}, strong_ordering::greater{1};
bool operator!=(std::strong_ordering o, int n) noexcept;
}
namespace Eq {
struct A {
bool operator==(const A&) const = default;
};
DEF(A() == A());
static_assert(noexcept(A() == A()));
struct B {
bool operator==(const B&) const;
};
struct C {
B b;
bool operator==(const C&) const = default;
};
DEF(C() == C());
static_assert(!noexcept(C() == C()));
// Ensure we do not trigger odr-use from exception specification computation.
template<typename T> struct D {
bool operator==(const D &) const {
typename T::error error; // expected-error {{no type}}
}
};
struct E {
D<E> d;
bool operator==(const E&) const = default;
};
static_assert(!noexcept(E() == E()));
// (but we do when defining the function).
struct F {
D<F> d;
bool operator==(const F&) const = default; // expected-note {{in instantiation}}
};
bool equal = F() == F();
static_assert(!noexcept(F() == F()));
}
namespace Spaceship {
struct X {
friend std::strong_ordering operator<=>(X, X);
};
struct Y : X {
friend std::strong_ordering operator<=>(Y, Y) = default;
};
DEF(Y() <=> Y());
static_assert(!noexcept(Y() <=> Y()));
struct ThrowingCmpCat {
ThrowingCmpCat(std::strong_ordering);
operator std::strong_ordering();
};
bool operator!=(ThrowingCmpCat o, int n) noexcept;
struct A {
friend ThrowingCmpCat operator<=>(A, A) noexcept;
};
struct B {
A a;
std::strong_ordering operator<=>(const B&) const = default;
};
DEF(B() <=> B());
static_assert(!noexcept(B() <=> B()));
struct C {
int n;
ThrowingCmpCat operator<=>(const C&) const = default;
};
DEF(C() <=> C());
static_assert(!noexcept(C() <=> C()));
struct D {
int n;
std::strong_ordering operator<=>(const D&) const = default;
};
DEF(D() <=> D());
static_assert(noexcept(D() <=> D()));
struct ThrowingCmpCat2 {
ThrowingCmpCat2(std::strong_ordering) noexcept;
operator std::strong_ordering() noexcept;
};
bool operator!=(ThrowingCmpCat2 o, int n);
struct E {
friend ThrowingCmpCat2 operator<=>(E, E) noexcept;
};
struct F {
E e;
std::strong_ordering operator<=>(const F&) const = default;
};
DEF(F() <=> F());
static_assert(noexcept(F() <=> F()));
struct G {
int n;
ThrowingCmpCat2 operator<=>(const G&) const = default;
};
DEF(G() <=> G());
static_assert(!noexcept(G() <=> G()));
}
namespace Synth {
struct A {
friend bool operator==(A, A) noexcept;
friend bool operator<(A, A) noexcept;
};
struct B {
A a;
friend std::strong_ordering operator<=>(B, B) = default;
};
std::strong_ordering operator<=>(B, B) noexcept;
struct C {
friend bool operator==(C, C);
friend bool operator<(C, C) noexcept;
};
struct D {
C c;
friend std::strong_ordering operator<=>(D, D) = default; // expected-note {{previous}}
};
std::strong_ordering operator<=>(D, D) noexcept; // expected-error {{does not match}}
struct E {
friend bool operator==(E, E) noexcept;
friend bool operator<(E, E);
};
struct F {
E e;
friend std::strong_ordering operator<=>(F, F) = default; // expected-note {{previous}}
};
std::strong_ordering operator<=>(F, F) noexcept; // expected-error {{does not match}}
}
namespace Secondary {
struct A {
friend bool operator==(A, A);
friend bool operator!=(A, A) = default; // expected-note {{previous}}
friend int operator<=>(A, A);
friend bool operator<(A, A) = default; // expected-note {{previous}}
friend bool operator<=(A, A) = default; // expected-note {{previous}}
friend bool operator>(A, A) = default; // expected-note {{previous}}
friend bool operator>=(A, A) = default; // expected-note {{previous}}
};
bool operator!=(A, A) noexcept; // expected-error {{does not match}}
bool operator<(A, A) noexcept; // expected-error {{does not match}}
bool operator<=(A, A) noexcept; // expected-error {{does not match}}
bool operator>(A, A) noexcept; // expected-error {{does not match}}
bool operator>=(A, A) noexcept; // expected-error {{does not match}}
struct B {
friend bool operator==(B, B) noexcept;
friend bool operator!=(B, B) = default;
friend int operator<=>(B, B) noexcept;
friend bool operator<(B, B) = default;
friend bool operator<=(B, B) = default;
friend bool operator>(B, B) = default;
friend bool operator>=(B, B) = default;
};
bool operator!=(B, B) noexcept;
bool operator<(B, B) noexcept;
bool operator<=(B, B) noexcept;
bool operator>(B, B) noexcept;
bool operator>=(B, B) noexcept;
}
// Check that we attempt to define a defaulted comparison before trying to
// compute its exception specification.
namespace DefineBeforeComputingExceptionSpec {
template<int> struct A {
A();
A(const A&) = delete; // expected-note 3{{here}}
friend bool operator==(A, A); // expected-note 3{{passing}}
friend bool operator!=(const A&, const A&) = default; // expected-error 3{{call to deleted constructor}}
};
bool a0 = A<0>() != A<0>(); // expected-note {{in defaulted equality comparison operator}}
bool a1 = operator!=(A<1>(), A<1>()); // expected-note {{in defaulted equality comparison operator}}
template struct A<2>;
bool operator!=(const A<2>&, const A<2>&) noexcept; // expected-note {{in evaluation of exception specification}}
template<int> struct B {
B();
B(const B&) = delete; // expected-note 3{{here}}
friend bool operator==(B, B); // expected-note 3{{passing}}
bool operator!=(const B&) const = default; // expected-error 3{{call to deleted constructor}}
};
bool b0 = B<0>() != B<0>(); // expected-note {{in defaulted equality comparison operator}}
bool b1 = B<1>().operator!=(B<1>()); // expected-note {{in defaulted equality comparison operator}}
int b2 = sizeof(&B<2>::operator!=); // expected-note {{in evaluation of exception specification}}
}