| // RUN: %clang_cc1 -std=c++2a -verify %s -fcxx-exceptions -triple=x86_64-linux-gnu -Wno-mismatched-new-delete |
| |
| #include "Inputs/std-compare.h" |
| |
| namespace std { |
| struct type_info; |
| struct destroying_delete_t { |
| explicit destroying_delete_t() = default; |
| } inline constexpr destroying_delete{}; |
| struct nothrow_t { |
| explicit nothrow_t() = default; |
| } inline constexpr nothrow{}; |
| using size_t = decltype(sizeof(0)); |
| enum class align_val_t : size_t {}; |
| }; |
| |
| [[nodiscard]] void *operator new(std::size_t, const std::nothrow_t&) noexcept; |
| [[nodiscard]] void *operator new(std::size_t, std::align_val_t, const std::nothrow_t&) noexcept; |
| [[nodiscard]] void *operator new[](std::size_t, const std::nothrow_t&) noexcept; |
| [[nodiscard]] void *operator new[](std::size_t, std::align_val_t, const std::nothrow_t&) noexcept; |
| void operator delete(void*, const std::nothrow_t&) noexcept; |
| void operator delete(void*, std::align_val_t, const std::nothrow_t&) noexcept; |
| void operator delete[](void*, const std::nothrow_t&) noexcept; |
| void operator delete[](void*, std::align_val_t, const std::nothrow_t&) noexcept; |
| |
| // Helper to print out values for debugging. |
| constexpr void not_defined(); |
| template<typename T> constexpr void print(T) { not_defined(); } |
| |
| namespace ThreeWayComparison { |
| struct A { |
| int n; |
| constexpr friend int operator<=>(const A &a, const A &b) { |
| return a.n < b.n ? -1 : a.n > b.n ? 1 : 0; |
| } |
| }; |
| static_assert(A{1} <=> A{2} < 0); |
| static_assert(A{2} <=> A{1} > 0); |
| static_assert(A{2} <=> A{2} == 0); |
| |
| static_assert(1 <=> 2 < 0); |
| static_assert(2 <=> 1 > 0); |
| static_assert(1 <=> 1 == 0); |
| constexpr int k = (1 <=> 1, 0); |
| // expected-warning@-1 {{three-way comparison result unused}} |
| |
| static_assert(std::strong_ordering::equal == 0); |
| |
| constexpr void f() { |
| void(1 <=> 1); |
| } |
| |
| struct MemPtr { |
| void foo() {} |
| void bar() {} |
| int data; |
| int data2; |
| long data3; |
| }; |
| |
| struct MemPtr2 { |
| void foo() {} |
| void bar() {} |
| int data; |
| int data2; |
| long data3; |
| }; |
| using MemPtrT = void (MemPtr::*)(); |
| |
| using FnPtrT = void (*)(); |
| |
| void FnPtr1() {} |
| void FnPtr2() {} |
| |
| #define CHECK(...) ((__VA_ARGS__) ? void() : throw "error") |
| #define CHECK_TYPE(...) static_assert(__is_same(__VA_ARGS__)); |
| |
| constexpr bool test_constexpr_success = [] { |
| { |
| auto &EQ = std::strong_ordering::equal; |
| auto &LESS = std::strong_ordering::less; |
| auto &GREATER = std::strong_ordering::greater; |
| using SO = std::strong_ordering; |
| auto eq = (42 <=> 42); |
| CHECK_TYPE(decltype(eq), SO); |
| CHECK(eq.test_eq(EQ)); |
| |
| auto less = (-1 <=> 0); |
| CHECK_TYPE(decltype(less), SO); |
| CHECK(less.test_eq(LESS)); |
| |
| auto greater = (42l <=> 1u); |
| CHECK_TYPE(decltype(greater), SO); |
| CHECK(greater.test_eq(GREATER)); |
| } |
| { |
| using PO = std::partial_ordering; |
| auto EQUIV = PO::equivalent; |
| auto LESS = PO::less; |
| auto GREATER = PO::greater; |
| |
| auto eq = (42.0 <=> 42.0); |
| CHECK_TYPE(decltype(eq), PO); |
| CHECK(eq.test_eq(EQUIV)); |
| |
| auto less = (39.0 <=> 42.0); |
| CHECK_TYPE(decltype(less), PO); |
| CHECK(less.test_eq(LESS)); |
| |
| auto greater = (-10.123 <=> -101.1); |
| CHECK_TYPE(decltype(greater), PO); |
| CHECK(greater.test_eq(GREATER)); |
| } |
| { |
| using SE = std::strong_equality; |
| auto EQ = SE::equal; |
| auto NEQ = SE::nonequal; |
| |
| MemPtrT P1 = &MemPtr::foo; |
| MemPtrT P12 = &MemPtr::foo; |
| MemPtrT P2 = &MemPtr::bar; |
| MemPtrT P3 = nullptr; |
| |
| auto eq = (P1 <=> P12); |
| CHECK_TYPE(decltype(eq), SE); |
| CHECK(eq.test_eq(EQ)); |
| |
| auto neq = (P1 <=> P2); |
| CHECK_TYPE(decltype(eq), SE); |
| CHECK(neq.test_eq(NEQ)); |
| |
| auto eq2 = (P3 <=> nullptr); |
| CHECK_TYPE(decltype(eq2), SE); |
| CHECK(eq2.test_eq(EQ)); |
| } |
| { |
| using SE = std::strong_equality; |
| auto EQ = SE::equal; |
| auto NEQ = SE::nonequal; |
| |
| FnPtrT F1 = &FnPtr1; |
| FnPtrT F12 = &FnPtr1; |
| FnPtrT F2 = &FnPtr2; |
| FnPtrT F3 = nullptr; |
| |
| auto eq = (F1 <=> F12); |
| CHECK_TYPE(decltype(eq), SE); |
| CHECK(eq.test_eq(EQ)); |
| |
| auto neq = (F1 <=> F2); |
| CHECK_TYPE(decltype(neq), SE); |
| CHECK(neq.test_eq(NEQ)); |
| } |
| { // mixed nullptr tests |
| using SO = std::strong_ordering; |
| using SE = std::strong_equality; |
| |
| int x = 42; |
| int *xp = &x; |
| |
| MemPtrT mf = nullptr; |
| MemPtrT mf2 = &MemPtr::foo; |
| auto r3 = (mf <=> nullptr); |
| CHECK_TYPE(decltype(r3), std::strong_equality); |
| CHECK(r3.test_eq(SE::equal)); |
| } |
| |
| return true; |
| }(); |
| |
| template <auto LHS, auto RHS, bool ExpectTrue = false> |
| constexpr bool test_constexpr() { |
| using nullptr_t = decltype(nullptr); |
| using LHSTy = decltype(LHS); |
| using RHSTy = decltype(RHS); |
| // expected-note@+1 {{subexpression not valid in a constant expression}} |
| auto Res = (LHS <=> RHS); |
| if constexpr (__is_same(LHSTy, nullptr_t) || __is_same(RHSTy, nullptr_t)) { |
| CHECK_TYPE(decltype(Res), std::strong_equality); |
| } |
| if (ExpectTrue) |
| return Res == 0; |
| return Res != 0; |
| } |
| int dummy = 42; |
| int dummy2 = 101; |
| |
| constexpr bool tc1 = test_constexpr<nullptr, &dummy>(); |
| constexpr bool tc2 = test_constexpr<&dummy, nullptr>(); |
| |
| // OK, equality comparison only |
| constexpr bool tc3 = test_constexpr<&MemPtr::foo, nullptr>(); |
| constexpr bool tc4 = test_constexpr<nullptr, &MemPtr::foo>(); |
| constexpr bool tc5 = test_constexpr<&MemPtr::foo, &MemPtr::bar>(); |
| |
| constexpr bool tc6 = test_constexpr<&MemPtr::data, nullptr>(); |
| constexpr bool tc7 = test_constexpr<nullptr, &MemPtr::data>(); |
| constexpr bool tc8 = test_constexpr<&MemPtr::data, &MemPtr::data2>(); |
| |
| // expected-error@+1 {{must be initialized by a constant expression}} |
| constexpr bool tc9 = test_constexpr<&dummy, &dummy2>(); // expected-note {{in call}} |
| |
| template <class T, class R, class I> |
| constexpr T makeComplex(R r, I i) { |
| T res{r, i}; |
| return res; |
| }; |
| |
| template <class T, class ResultT> |
| constexpr bool complex_test(T x, T y, ResultT Expect) { |
| auto res = x <=> y; |
| CHECK_TYPE(decltype(res), ResultT); |
| return res.test_eq(Expect); |
| } |
| static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), |
| makeComplex<_Complex double>(0.0, 0.0), |
| std::weak_equality::equivalent)); |
| static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), |
| makeComplex<_Complex double>(1.0, 0.0), |
| std::weak_equality::nonequivalent)); |
| static_assert(complex_test(makeComplex<_Complex double>(0.0, 0.0), |
| makeComplex<_Complex double>(0.0, 1.0), |
| std::weak_equality::nonequivalent)); |
| static_assert(complex_test(makeComplex<_Complex int>(0, 0), |
| makeComplex<_Complex int>(0, 0), |
| std::strong_equality::equal)); |
| static_assert(complex_test(makeComplex<_Complex int>(0, 0), |
| makeComplex<_Complex int>(1, 0), |
| std::strong_equality::nonequal)); |
| // TODO: defaulted operator <=> |
| } // namespace ThreeWayComparison |
| |
| constexpr bool for_range_init() { |
| int k = 0; |
| for (int arr[3] = {1, 2, 3}; int n : arr) k += n; |
| return k == 6; |
| } |
| static_assert(for_range_init()); |
| |
| namespace Virtual { |
| struct NonZeroOffset { int padding = 123; }; |
| |
| constexpr void assert(bool b) { if (!b) throw 0; } |
| |
| // Ensure that we pick the right final overrider during construction. |
| struct A { |
| virtual constexpr char f() const { return 'A'; } |
| char a = f(); |
| constexpr ~A() { assert(f() == 'A'); } |
| }; |
| struct NoOverrideA : A {}; |
| struct B : NonZeroOffset, NoOverrideA { |
| virtual constexpr char f() const { return 'B'; } |
| char b = f(); |
| constexpr ~B() { assert(f() == 'B'); } |
| }; |
| struct NoOverrideB : B {}; |
| struct C : NonZeroOffset, A { |
| virtual constexpr char f() const { return 'C'; } |
| A *pba; |
| char c = ((A*)this)->f(); |
| char ba = pba->f(); |
| constexpr C(A *pba) : pba(pba) {} |
| constexpr ~C() { assert(f() == 'C'); } |
| }; |
| struct D : NonZeroOffset, NoOverrideB, C { // expected-warning {{inaccessible}} |
| virtual constexpr char f() const { return 'D'; } |
| char d = f(); |
| constexpr D() : C((B*)this) {} |
| constexpr ~D() { assert(f() == 'D'); } |
| }; |
| constexpr int n = (D(), 0); |
| constexpr D d; |
| static_assert(((B&)d).a == 'A'); |
| static_assert(((C&)d).a == 'A'); |
| static_assert(d.b == 'B'); |
| static_assert(d.c == 'C'); |
| // During the construction of C, the dynamic type of B's A is B. |
| static_assert(d.ba == 'B'); |
| static_assert(d.d == 'D'); |
| static_assert(d.f() == 'D'); |
| constexpr const A &a = (B&)d; |
| constexpr const B &b = d; |
| static_assert(a.f() == 'D'); |
| static_assert(b.f() == 'D'); |
| |
| // FIXME: It is unclear whether this should be permitted. |
| D d_not_constexpr; |
| static_assert(d_not_constexpr.f() == 'D'); // expected-error {{constant expression}} expected-note {{virtual function called on object 'd_not_constexpr' whose dynamic type is not constant}} |
| |
| // Check that we apply a proper adjustment for a covariant return type. |
| struct Covariant1 { |
| D d; |
| virtual const A *f() const; |
| }; |
| template<typename T> |
| struct Covariant2 : Covariant1 { |
| virtual const T *f() const; |
| }; |
| template<typename T> |
| struct Covariant3 : Covariant2<T> { |
| constexpr virtual const D *f() const { return &this->d; } |
| }; |
| |
| constexpr Covariant3<B> cb; |
| constexpr Covariant3<C> cc; |
| |
| constexpr const Covariant1 *cb1 = &cb; |
| constexpr const Covariant2<B> *cb2 = &cb; |
| static_assert(cb1->f()->a == 'A'); |
| static_assert(cb1->f() == (B*)&cb.d); |
| static_assert(cb1->f()->f() == 'D'); |
| static_assert(cb2->f()->b == 'B'); |
| static_assert(cb2->f() == &cb.d); |
| static_assert(cb2->f()->f() == 'D'); |
| |
| constexpr const Covariant1 *cc1 = &cc; |
| constexpr const Covariant2<C> *cc2 = &cc; |
| static_assert(cc1->f()->a == 'A'); |
| static_assert(cc1->f() == (C*)&cc.d); |
| static_assert(cc1->f()->f() == 'D'); |
| static_assert(cc2->f()->c == 'C'); |
| static_assert(cc2->f() == &cc.d); |
| static_assert(cc2->f()->f() == 'D'); |
| |
| static_assert(cb.f()->d == 'D'); |
| static_assert(cc.f()->d == 'D'); |
| |
| struct Abstract { |
| constexpr virtual void f() = 0; // expected-note {{declared here}} |
| constexpr Abstract() { do_it(); } // expected-note {{in call to}} |
| constexpr void do_it() { f(); } // expected-note {{pure virtual function 'Virtual::Abstract::f' called}} |
| }; |
| struct PureVirtualCall : Abstract { void f(); }; // expected-note {{in call to 'Abstract}} |
| constexpr PureVirtualCall pure_virtual_call; // expected-error {{constant expression}} expected-note {{in call to 'PureVirtualCall}} |
| } |
| |
| namespace DynamicCast { |
| struct A2 { virtual void a2(); }; |
| struct A : A2 { virtual void a(); }; |
| struct B : A {}; |
| struct C2 { virtual void c2(); }; |
| struct C : A, C2 { A *c = dynamic_cast<A*>(static_cast<C2*>(this)); }; |
| struct D { virtual void d(); }; |
| struct E { virtual void e(); }; |
| struct F : B, C, D, private E { void *f = dynamic_cast<void*>(static_cast<D*>(this)); }; |
| struct Padding { virtual void padding(); }; |
| struct G : Padding, F {}; |
| |
| constexpr G g; |
| |
| // During construction of C, A is unambiguous subobject of dynamic type C. |
| static_assert(g.c == (C*)&g); |
| // ... but in the complete object, the same is not true, so the runtime fails. |
| static_assert(dynamic_cast<const A*>(static_cast<const C2*>(&g)) == nullptr); |
| |
| // dynamic_cast<void*> produces a pointer to the object of the dynamic type. |
| static_assert(g.f == (void*)(F*)&g); |
| static_assert(dynamic_cast<const void*>(static_cast<const D*>(&g)) == &g); |
| |
| // expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}} |
| constexpr int d_a = (dynamic_cast<const A&>(static_cast<const D&>(g)), 0); // expected-error {{}} |
| |
| // Can navigate from A2 to its A... |
| static_assert(&dynamic_cast<A&>((A2&)(B&)g) == &(A&)(B&)g); |
| // ... and from B to its A ... |
| static_assert(&dynamic_cast<A&>((B&)g) == &(A&)(B&)g); |
| // ... but not from D. |
| // expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::A' is an ambiguous base class of dynamic type 'DynamicCast::G' of operand}} |
| static_assert(&dynamic_cast<A&>((D&)g) == &(A&)(B&)g); // expected-error {{}} |
| |
| // Can cast from A2 to sibling class D. |
| static_assert(&dynamic_cast<D&>((A2&)(B&)g) == &(D&)g); |
| |
| // Cannot cast from private base E to derived class F. |
| // expected-note@+1 {{reference dynamic_cast failed: static type 'DynamicCast::E' of operand is a non-public base class of dynamic type 'DynamicCast::G'}} |
| constexpr int e_f = (dynamic_cast<F&>((E&)g), 0); // expected-error {{}} |
| |
| // Cannot cast from B to private sibling E. |
| // expected-note@+1 {{reference dynamic_cast failed: 'DynamicCast::E' is a non-public base class of dynamic type 'DynamicCast::G' of operand}} |
| constexpr int b_e = (dynamic_cast<E&>((B&)g), 0); // expected-error {{}} |
| |
| struct Unrelated { virtual void unrelated(); }; |
| // expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'DynamicCast::Unrelated'}} |
| constexpr int b_unrelated = (dynamic_cast<Unrelated&>((B&)g), 0); // expected-error {{}} |
| // expected-note@+1 {{reference dynamic_cast failed: dynamic type 'DynamicCast::G' of operand does not have a base class of type 'DynamicCast::Unrelated'}} |
| constexpr int e_unrelated = (dynamic_cast<Unrelated&>((E&)g), 0); // expected-error {{}} |
| } |
| |
| namespace TypeId { |
| struct A { |
| const std::type_info &ti = typeid(*this); |
| }; |
| struct A2 : A {}; |
| static_assert(&A().ti == &typeid(A)); |
| static_assert(&typeid((A2())) == &typeid(A2)); |
| extern A2 extern_a2; |
| static_assert(&typeid(extern_a2) == &typeid(A2)); |
| |
| constexpr A2 a2; |
| constexpr const A &a1 = a2; |
| static_assert(&typeid(a1) == &typeid(A)); |
| |
| struct B { |
| virtual void f(); |
| const std::type_info &ti1 = typeid(*this); |
| }; |
| struct B2 : B { |
| const std::type_info &ti2 = typeid(*this); |
| }; |
| static_assert(&B2().ti1 == &typeid(B)); |
| static_assert(&B2().ti2 == &typeid(B2)); |
| extern B2 extern_b2; |
| // expected-note@+1 {{typeid applied to object 'extern_b2' whose dynamic type is not constant}} |
| static_assert(&typeid(extern_b2) == &typeid(B2)); // expected-error {{constant expression}} |
| |
| constexpr B2 b2; |
| constexpr const B &b1 = b2; |
| static_assert(&typeid(b1) == &typeid(B2)); |
| |
| constexpr bool side_effects() { |
| // Not polymorphic nor a glvalue. |
| bool OK = true; |
| (void)typeid(OK = false, A2()); // expected-warning {{has no effect}} |
| if (!OK) return false; |
| |
| // Not polymorphic. |
| A2 a2; |
| (void)typeid(OK = false, a2); // expected-warning {{has no effect}} |
| if (!OK) return false; |
| |
| // Not a glvalue. |
| (void)typeid(OK = false, B2()); // expected-warning {{has no effect}} |
| if (!OK) return false; |
| |
| // Polymorphic glvalue: operand evaluated. |
| OK = false; |
| B2 b2; |
| (void)typeid(OK = true, b2); // expected-warning {{will be evaluated}} |
| return OK; |
| } |
| static_assert(side_effects()); |
| } |
| |
| namespace Union { |
| struct Base { |
| int y; // expected-note 2{{here}} |
| }; |
| struct A : Base { |
| int x; |
| int arr[3]; |
| union { int p, q; }; |
| }; |
| union B { |
| A a; |
| int b; |
| }; |
| constexpr int read_wrong_member() { // expected-error {{never produces a constant}} |
| B b = {.b = 1}; |
| return b.a.x; // expected-note {{read of member 'a' of union with active member 'b'}} |
| } |
| constexpr int change_member() { |
| B b = {.b = 1}; |
| b.a.x = 1; |
| return b.a.x; |
| } |
| static_assert(change_member() == 1); |
| constexpr int change_member_then_read_wrong_member() { // expected-error {{never produces a constant}} |
| B b = {.b = 1}; |
| b.a.x = 1; |
| return b.b; // expected-note {{read of member 'b' of union with active member 'a'}} |
| } |
| constexpr int read_wrong_member_indirect() { // expected-error {{never produces a constant}} |
| B b = {.b = 1}; |
| int *p = &b.a.y; |
| return *p; // expected-note {{read of member 'a' of union with active member 'b'}} |
| } |
| constexpr int read_uninitialized() { |
| B b = {.b = 1}; |
| int *p = &b.a.y; |
| b.a.x = 1; |
| return *p; // expected-note {{read of uninitialized object}} |
| } |
| static_assert(read_uninitialized() == 0); // expected-error {{constant}} expected-note {{in call}} |
| constexpr void write_wrong_member_indirect() { // expected-error {{never produces a constant}} |
| B b = {.b = 1}; |
| int *p = &b.a.y; |
| *p = 1; // expected-note {{assignment to member 'a' of union with active member 'b'}} |
| } |
| constexpr int write_uninitialized() { |
| B b = {.b = 1}; |
| int *p = &b.a.y; |
| b.a.x = 1; |
| *p = 1; |
| return *p; |
| } |
| static_assert(write_uninitialized() == 1); |
| constexpr int change_member_indirectly() { |
| B b = {.b = 1}; |
| b.a.arr[1] = 1; |
| int &r = b.a.y; |
| r = 123; |
| |
| b.b = 2; |
| b.a.y = 3; |
| b.a.arr[2] = 4; |
| return b.a.arr[2]; |
| } |
| static_assert(change_member_indirectly() == 4); |
| constexpr B return_uninit() { |
| B b = {.b = 1}; |
| b.a.x = 2; |
| return b; |
| } |
| constexpr B uninit = return_uninit(); // expected-error {{constant expression}} expected-note {{subobject of type 'int' is not initialized}} |
| static_assert(return_uninit().a.x == 2); |
| constexpr A return_uninit_struct() { |
| B b = {.b = 1}; |
| b.a.x = 2; |
| return b.a; // expected-note {{in call to 'A(b.a)'}} expected-note {{subobject of type 'int' is not initialized}} |
| } |
| // Note that this is rejected even though return_uninit() is accepted, and |
| // return_uninit() copies the same stuff wrapped in a union. |
| // |
| // Copying a B involves copying the object representation of the union, but |
| // copying an A invokes a copy constructor that copies the object |
| // elementwise, and reading from b.a.y is undefined. |
| static_assert(return_uninit_struct().x == 2); // expected-error {{constant expression}} expected-note {{in call}} |
| constexpr B return_init_all() { |
| B b = {.b = 1}; |
| b.a.x = 2; |
| b.a.y = 3; |
| b.a.arr[0] = 4; |
| b.a.arr[1] = 5; |
| b.a.arr[2] = 6; |
| return b; |
| } |
| static_assert(return_init_all().a.x == 2); |
| static_assert(return_init_all().a.y == 3); |
| static_assert(return_init_all().a.arr[0] == 4); |
| static_assert(return_init_all().a.arr[1] == 5); |
| static_assert(return_init_all().a.arr[2] == 6); |
| static_assert(return_init_all().a.p == 7); // expected-error {{}} expected-note {{read of member 'p' of union with no active member}} |
| static_assert(return_init_all().a.q == 8); // expected-error {{}} expected-note {{read of member 'q' of union with no active member}} |
| constexpr B init_all = return_init_all(); |
| |
| constexpr bool test_no_member_change = []{ |
| union U { char dummy = {}; }; |
| U u1; |
| U u2; |
| u1 = u2; |
| return true; |
| }(); |
| |
| struct S1 { |
| int n; |
| }; |
| struct S2 : S1 {}; |
| struct S3 : S2 {}; |
| void f() { |
| S3 s; |
| s.n = 0; |
| } |
| |
| union ref_member_1 { |
| int a; |
| int b; |
| }; |
| struct ref_member_2 { |
| ref_member_1 &&r; |
| }; |
| union ref_member_3 { |
| ref_member_2 a, b; |
| }; |
| constexpr int ref_member_test_1() { |
| ref_member_3 r = {.a = {.r = {.a = 1}}}; |
| r.a.r.b = 2; |
| return r.a.r.b; |
| } |
| static_assert(ref_member_test_1() == 2); |
| constexpr int ref_member_test_2() { // expected-error {{never produces a constant}} |
| ref_member_3 r = {.a = {.r = {.a = 1}}}; |
| // FIXME: This note isn't great. The 'read' here is reading the referent of the reference. |
| r.b.r.b = 2; // expected-note {{read of member 'b' of union with active member 'a'}} |
| return r.b.r.b; |
| } |
| } |
| |
| namespace TwosComplementShifts { |
| using uint32 = __UINT32_TYPE__; |
| using int32 = __INT32_TYPE__; |
| static_assert(uint32(int32(0x1234) << 16) == 0x12340000); |
| static_assert(uint32(int32(0x1234) << 19) == 0x91a00000); |
| static_assert(uint32(int32(0x1234) << 20) == 0x23400000); // expected-warning {{requires 34 bits}} |
| static_assert(uint32(int32(0x1234) << 24) == 0x34000000); // expected-warning {{requires 38 bits}} |
| static_assert(uint32(int32(-1) << 31) == 0x80000000); |
| |
| static_assert(-1 >> 1 == -1); |
| static_assert(-1 >> 31 == -1); |
| static_assert(-2 >> 1 == -1); |
| static_assert(-3 >> 1 == -2); |
| static_assert(-4 >> 1 == -2); |
| } |
| |
| namespace Uninit { |
| constexpr int f(bool init) { |
| int a; |
| if (init) |
| a = 1; |
| return a; // expected-note {{read of uninitialized object}} |
| } |
| static_assert(f(true) == 1); |
| static_assert(f(false) == 1); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| struct X { |
| int n; // expected-note {{declared here}} |
| constexpr X(bool init) { |
| if (init) n = 123; |
| } |
| }; |
| constinit X x1(true); |
| constinit X x2(false); // expected-error {{constant initializer}} expected-note {{constinit}} expected-note {{subobject of type 'int' is not initialized}} |
| |
| struct Y { |
| struct Z { int n; }; // expected-note {{here}} |
| Z z1; |
| Z z2; |
| Z z3; |
| // OK: the lifetime of z1 (and its members) start before the initializer of |
| // z2 runs. |
| constexpr Y() : z2{ (z1.n = 1, z1.n + 1) } { z3.n = 3; } |
| // Not OK: z3 is not in its lifetime when the initializer of z2 runs. |
| constexpr Y(int) : z2{ |
| (z3.n = 1, // expected-note {{assignment to object outside its lifetime}} |
| z3.n + 1) // expected-warning {{uninitialized}} |
| } { z1.n = 3; } |
| constexpr Y(int, int) : z2{} {} |
| }; |
| // FIXME: This is working around clang not implementing DR2026. With that |
| // fixed, we should be able to test this without the injected copy. |
| constexpr Y copy(Y y) { return y; } // expected-note {{in call to 'Y(y)'}} expected-note {{subobject of type 'int' is not initialized}} |
| constexpr Y y1 = copy(Y()); |
| static_assert(y1.z1.n == 1 && y1.z2.n == 2 && y1.z3.n == 3); |
| |
| constexpr Y y2 = copy(Y(0)); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| static_assert(Y(0,0).z2.n == 0); |
| static_assert(Y(0,0).z1.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}} |
| static_assert(Y(0,0).z3.n == 0); // expected-error {{constant expression}} expected-note {{read of uninitialized object}} |
| |
| static_assert(copy(Y(0,0)).z2.n == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| constexpr unsigned char not_even_unsigned_char() { |
| unsigned char c; |
| return c; // expected-note {{read of uninitialized object}} |
| } |
| constexpr unsigned char x = not_even_unsigned_char(); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| constexpr int switch_var(int n) { |
| switch (n) { |
| case 1: |
| int a; |
| a = n; |
| return a; |
| |
| case 2: |
| a = n; |
| return a; |
| } |
| } |
| constexpr int s1 = switch_var(1); |
| constexpr int s2 = switch_var(2); |
| static_assert(s1 == 1 && s2 == 2); |
| |
| constexpr bool switch_into_init_stmt() { |
| switch (1) { |
| if (int n; false) { |
| for (int m; false;) { |
| case 1: |
| n = m = 1; |
| return n == 1 && m == 1; |
| } |
| } |
| } |
| } |
| static_assert(switch_into_init_stmt()); |
| } |
| |
| namespace dtor { |
| void lifetime_extension() { |
| struct X { constexpr ~X() {} }; |
| X &&a = X(); |
| } |
| |
| template<typename T> constexpr T &&ref(T &&t) { return (T&&)t; } |
| |
| struct Buf { |
| char buf[64]; |
| int n = 0; |
| constexpr void operator+=(char c) { buf[n++] = c; } |
| constexpr bool operator==(const char *str) const { |
| return str[n] == 0 && __builtin_memcmp(str, buf, n) == 0; |
| } |
| constexpr bool operator!=(const char *str) const { return !operator==(str); } |
| }; |
| |
| struct A { |
| constexpr A(Buf &buf, char c) : buf(buf), c(c) { buf += c; } |
| constexpr ~A() { buf += c; } |
| constexpr operator bool() const { return true; } |
| Buf &buf; |
| char c; |
| }; |
| |
| constexpr bool dtor_calls_dtor() { |
| union U { |
| constexpr U(Buf &buf) : u(buf, 'u') { buf += 'U'; } |
| constexpr ~U() { u.buf += 'U'; } |
| A u, v; |
| }; |
| |
| struct B : A { |
| A c, &&d, e; |
| union { |
| A f; |
| }; |
| U u; |
| constexpr B(Buf &buf) |
| : A(buf, 'a'), c(buf, 'c'), d(ref(A(buf, 'd'))), e(A(buf, 'e')), f(buf, 'f'), u(buf) { |
| buf += 'b'; |
| } |
| constexpr ~B() { |
| buf += 'b'; |
| } |
| }; |
| |
| Buf buf; |
| { |
| B b(buf); |
| if (buf != "acddefuUb") |
| return false; |
| } |
| if (buf != "acddefuUbbUeca") |
| return false; |
| return true; |
| } |
| static_assert(dtor_calls_dtor()); |
| |
| constexpr void abnormal_termination(Buf &buf) { |
| struct Indestructible { |
| constexpr ~Indestructible(); // not defined |
| }; |
| |
| A a(buf, 'a'); |
| A(buf, 'b'); |
| int n = 0; |
| for (A &&c = A(buf, 'c'); A d = A(buf, 'd'); A(buf, 'e')) { |
| switch (A f(buf, 'f'); A g = A(buf, 'g')) { // expected-warning {{boolean}} |
| case false: { |
| A x(buf, 'x'); |
| } |
| |
| case true: { |
| A h(buf, 'h'); |
| switch (n++) { |
| case 0: |
| break; |
| case 1: |
| continue; |
| case 2: |
| return; |
| } |
| break; |
| } |
| |
| default: |
| Indestructible indest; |
| } |
| |
| A j = (A(buf, 'i'), A(buf, 'j')); |
| } |
| } |
| |
| constexpr bool check_abnormal_termination() { |
| Buf buf = {}; |
| abnormal_termination(buf); |
| return buf == |
| "abbc" |
| "dfgh" /*break*/ "hgfijijeed" |
| "dfgh" /*continue*/ "hgfeed" |
| "dfgh" /*return*/ "hgfd" |
| "ca"; |
| } |
| static_assert(check_abnormal_termination()); |
| |
| constexpr bool run_dtors_on_array_filler() { |
| struct S { |
| int times_destroyed = 0; |
| constexpr ~S() { if (++times_destroyed != 1) throw "oops"; } |
| }; |
| S s[3]; |
| return true; |
| } |
| static_assert(run_dtors_on_array_filler()); |
| |
| // Ensure that we can handle temporary cleanups for array temporaries. |
| struct ArrElem { constexpr ~ArrElem() {} }; |
| using Arr = ArrElem[3]; |
| static_assert((Arr{}, true)); |
| } |
| |
| namespace dynamic_alloc { |
| constexpr int *p = // expected-error {{constant}} expected-note {{pointer to heap-allocated object is not a constant expression}} |
| new int; // expected-note {{heap allocation performed here}} |
| |
| constexpr int f(int n) { |
| int *p = new int[n]; |
| for (int i = 0; i != n; ++i) { |
| p[i] = i; |
| } |
| int k = 0; |
| for (int i = 0; i != n; ++i) { |
| k += p[i]; |
| } |
| delete[] p; |
| return k; |
| } |
| static_assert(f(123) == 123 * 122 / 2); |
| |
| constexpr bool nvdtor() { // expected-error {{never produces a constant expression}} |
| struct S { |
| constexpr ~S() {} |
| }; |
| struct T : S {}; |
| delete (S*)new T; // expected-note {{delete of object with dynamic type 'T' through pointer to base class type 'S' with non-virtual destructor}} |
| return true; |
| } |
| |
| constexpr int vdtor_1() { |
| int a; |
| struct S { |
| constexpr S(int *p) : p(p) {} |
| constexpr virtual ~S() { *p = 1; } |
| int *p; |
| }; |
| struct T : S { |
| // implicit destructor defined eagerly because it is constexpr and virtual |
| using S::S; |
| }; |
| delete (S*)new T(&a); |
| return a; |
| } |
| static_assert(vdtor_1() == 1); |
| |
| constexpr int vdtor_2() { |
| int a = 0; |
| struct S { constexpr virtual ~S() {} }; |
| struct T : S { |
| constexpr T(int *p) : p(p) {} |
| constexpr ~T() { ++*p; } |
| int *p; |
| }; |
| S *p = new T{&a}; |
| delete p; |
| return a; |
| } |
| static_assert(vdtor_2() == 1); |
| |
| constexpr int vdtor_3(int mode) { |
| int a = 0; |
| struct S { constexpr virtual ~S() {} }; |
| struct T : S { |
| constexpr T(int *p) : p(p) {} |
| constexpr ~T() { ++*p; } |
| int *p; |
| }; |
| S *p = new T[3]{&a, &a, &a}; // expected-note 2{{heap allocation}} |
| switch (mode) { |
| case 0: |
| delete p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}} |
| break; |
| case 1: |
| // FIXME: This diagnosic isn't great; we should mention the cast to S* |
| // somewhere in here. |
| delete[] p; // expected-note {{delete of pointer to subobject '&{*new T [3]#0}[0]'}} |
| break; |
| case 2: |
| delete (T*)p; // expected-note {{non-array delete used to delete pointer to array object of type 'T [3]'}} |
| break; |
| case 3: |
| delete[] (T*)p; |
| break; |
| } |
| return a; |
| } |
| static_assert(vdtor_3(0) == 3); // expected-error {{}} expected-note {{in call}} |
| static_assert(vdtor_3(1) == 3); // expected-error {{}} expected-note {{in call}} |
| static_assert(vdtor_3(2) == 3); // expected-error {{}} expected-note {{in call}} |
| static_assert(vdtor_3(3) == 3); |
| |
| constexpr void delete_mismatch() { // expected-error {{never produces a constant expression}} |
| delete[] // expected-note {{array delete used to delete pointer to non-array object of type 'int'}} |
| new int; // expected-note {{allocation}} |
| } |
| |
| template<typename T> |
| constexpr T dynarray(int elems, int i) { |
| T *p; |
| if constexpr (sizeof(T) == 1) |
| p = new T[elems]{"fox"}; // expected-note {{evaluated array bound 3 is too small to hold 4 explicitly initialized elements}} |
| else |
| p = new T[elems]{1, 2, 3}; // expected-note {{evaluated array bound 2 is too small to hold 3 explicitly initialized elements}} |
| T n = p[i]; // expected-note 4{{past-the-end}} |
| delete [] p; |
| return n; |
| } |
| static_assert(dynarray<int>(4, 0) == 1); |
| static_assert(dynarray<int>(4, 1) == 2); |
| static_assert(dynarray<int>(4, 2) == 3); |
| static_assert(dynarray<int>(4, 3) == 0); |
| static_assert(dynarray<int>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(dynarray<int>(3, 2) == 3); |
| static_assert(dynarray<int>(3, 3) == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(dynarray<int>(2, 1) == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(dynarray<char>(5, 0) == 'f'); |
| static_assert(dynarray<char>(5, 1) == 'o'); |
| static_assert(dynarray<char>(5, 2) == 'x'); |
| static_assert(dynarray<char>(5, 3) == 0); // (from string) |
| static_assert(dynarray<char>(5, 4) == 0); // (from filler) |
| static_assert(dynarray<char>(5, 5) == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(dynarray<char>(4, 0) == 'f'); |
| static_assert(dynarray<char>(4, 1) == 'o'); |
| static_assert(dynarray<char>(4, 2) == 'x'); |
| static_assert(dynarray<char>(4, 3) == 0); |
| static_assert(dynarray<char>(4, 4) == 0); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(dynarray<char>(3, 2) == 'x'); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| constexpr bool run_dtors_on_array_filler() { |
| struct S { |
| int times_destroyed = 0; |
| constexpr ~S() { if (++times_destroyed != 1) throw "oops"; } |
| }; |
| delete[] new S[3]; |
| return true; |
| } |
| static_assert(run_dtors_on_array_filler()); |
| |
| constexpr bool erroneous_array_bound(long long n) { |
| delete[] new int[n]; // expected-note {{array bound -1 is negative}} expected-note {{array bound 4611686018427387904 is too large}} |
| return true; |
| } |
| static_assert(erroneous_array_bound(3)); |
| static_assert(erroneous_array_bound(0)); |
| static_assert(erroneous_array_bound(-1)); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(erroneous_array_bound(1LL << 62)); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| constexpr bool erroneous_array_bound_nothrow(long long n) { |
| int *p = new (std::nothrow) int[n]; |
| bool result = p != 0; |
| delete[] p; |
| return result; |
| } |
| static_assert(erroneous_array_bound_nothrow(3)); |
| static_assert(erroneous_array_bound_nothrow(0)); |
| static_assert(!erroneous_array_bound_nothrow(-1)); |
| static_assert(!erroneous_array_bound_nothrow(1LL << 62)); |
| |
| constexpr bool evaluate_nothrow_arg() { |
| bool ok = false; |
| delete new ((ok = true, std::nothrow)) int; |
| return ok; |
| } |
| static_assert(evaluate_nothrow_arg()); |
| |
| constexpr void double_delete() { // expected-error {{never produces a constant expression}} |
| int *p = new int; |
| delete p; |
| delete p; // expected-note {{delete of pointer that has already been deleted}} |
| } |
| constexpr bool super_secret_double_delete() { |
| struct A { |
| constexpr ~A() { delete this; } // expected-note {{destruction of object that is already being destroyed}} expected-note {{in call}} |
| }; |
| delete new A; // expected-note {{in call}} |
| return true; |
| } |
| static_assert(super_secret_double_delete()); // expected-error {{constant expression}} expected-note {{in call}} |
| |
| constexpr void use_after_free() { // expected-error {{never produces a constant expression}} |
| int *p = new int; |
| delete p; |
| *p = 1; // expected-note {{assignment to heap allocated object that has been deleted}} |
| } |
| constexpr void use_after_free_2() { // expected-error {{never produces a constant expression}} |
| struct X { constexpr void f() {} }; |
| X *p = new X; |
| delete p; |
| p->f(); // expected-note {{member call on heap allocated object that has been deleted}} |
| } |
| |
| template<typename T> struct X { |
| std::size_t n; |
| char *p; |
| void dependent(); |
| }; |
| template<typename T> void X<T>::dependent() { |
| char *p; |
| // Ensure that we don't try to evaluate these for overflow and crash. These |
| // are all value-dependent expressions. |
| p = new char[n]; |
| p = new (n) char[n]; |
| p = new char(n); |
| } |
| } |
| |
| struct placement_new_arg {}; |
| void *operator new(std::size_t, placement_new_arg); |
| void operator delete(void*, placement_new_arg); |
| |
| namespace placement_new_delete { |
| struct ClassSpecificNew { |
| void *operator new(std::size_t); |
| }; |
| struct ClassSpecificDelete { |
| void operator delete(void*); |
| }; |
| struct DestroyingDelete { |
| void operator delete(DestroyingDelete*, std::destroying_delete_t); |
| }; |
| struct alignas(64) Overaligned {}; |
| |
| constexpr bool ok() { |
| delete new Overaligned; |
| delete ::new ClassSpecificNew; |
| ::delete new ClassSpecificDelete; |
| ::delete new DestroyingDelete; |
| return true; |
| } |
| static_assert(ok()); |
| |
| constexpr bool bad(int which) { |
| switch (which) { |
| case 0: |
| delete new (placement_new_arg{}) int; // expected-note {{call to placement 'operator new'}} |
| break; |
| |
| case 1: |
| delete new ClassSpecificNew; // expected-note {{call to class-specific 'operator new'}} |
| break; |
| |
| case 2: |
| delete new ClassSpecificDelete; // expected-note {{call to class-specific 'operator delete'}} |
| break; |
| |
| case 3: |
| delete new DestroyingDelete; // expected-note {{call to class-specific 'operator delete'}} |
| break; |
| |
| case 4: |
| // FIXME: This technically follows the standard's rules, but it seems |
| // unreasonable to expect implementations to support this. |
| delete new (std::align_val_t{64}) Overaligned; // expected-note {{placement new expression is not yet supported}} |
| break; |
| } |
| |
| return true; |
| } |
| static_assert(bad(0)); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(bad(1)); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(bad(2)); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(bad(3)); // expected-error {{constant expression}} expected-note {{in call}} |
| static_assert(bad(4)); // expected-error {{constant expression}} expected-note {{in call}} |
| } |
| |
| namespace delete_random_things { |
| static_assert((delete new int, true)); |
| static_assert((delete (int*)0, true)); |
| int n; // expected-note {{declared here}} |
| static_assert((delete &n, true)); // expected-error {{}} expected-note {{delete of pointer '&n' that does not point to a heap-allocated object}} |
| struct A { int n; }; |
| static_assert((delete &(new A)->n, true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new delete_random_things::A#0}.n'}} |
| static_assert((delete (new int + 1), true)); // expected-error {{}} expected-note {{delete of pointer '&{*new int#0} + 1' that does not point to complete object}} |
| static_assert((delete[] (new int[3] + 1), true)); // expected-error {{}} expected-note {{delete of pointer to subobject '&{*new int [3]#0}[1]'}} |
| static_assert((delete &(int&)(int&&)0, true)); // expected-error {{}} expected-note {{delete of pointer '&0' that does not point to a heap-allocated object}} expected-note {{temporary created here}} |
| } |
| |
| namespace value_dependent_delete { |
| template<typename T> void f(T *p) { |
| int arr[(delete p, 0)]; |
| } |
| } |
| |
| namespace memory_leaks { |
| static_assert(*new bool(true)); // expected-error {{}} expected-note {{allocation performed here was not deallocated}} |
| |
| constexpr bool *f() { return new bool(true); } // expected-note {{allocation performed here was not deallocated}} |
| static_assert(*f()); // expected-error {{}} |
| |
| struct UP { |
| bool *p; |
| constexpr ~UP() { delete p; } |
| constexpr bool &operator*() { return *p; } |
| }; |
| constexpr UP g() { return {new bool(true)}; } |
| static_assert(*g()); // ok |
| |
| constexpr bool h(UP p) { return *p; } |
| static_assert(h({new bool(true)})); // ok |
| } |
| |
| namespace dtor_call { |
| struct A { int n; }; |
| constexpr void f() { // expected-error {{never produces a constant expression}} |
| A a; // expected-note {{destroying object 'a' whose lifetime has already ended}} |
| a.~A(); |
| } |
| union U { A a; }; |
| constexpr void g() { |
| U u; |
| u.a.n = 3; |
| u.a.~A(); |
| // There's now effectively no active union member, but we model it as if |
| // 'a' is still the active union member (but its lifetime has ended). |
| u.a.n = 4; // Start lifetime of 'a' again. |
| u.a.~A(); |
| } |
| static_assert((g(), true)); |
| |
| constexpr bool pseudo() { |
| using T = bool; |
| bool b = false; |
| // This does evaluate the store to 'b'... |
| (b = true).~T(); |
| // ... but does not end the lifetime of the object. |
| return b; |
| } |
| static_assert(pseudo()); |
| |
| constexpr void use_after_destroy() { |
| A a; |
| a.~A(); |
| A b = a; // expected-note {{in call}} expected-note {{read of object outside its lifetime}} |
| } |
| static_assert((use_after_destroy(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void double_destroy() { |
| A a; |
| a.~A(); |
| a.~A(); // expected-note {{destruction of object outside its lifetime}} |
| } |
| static_assert((double_destroy(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| struct X { char *p; constexpr ~X() { *p++ = 'X'; } }; |
| struct Y : X { int y; virtual constexpr ~Y() { *p++ = 'Y'; } }; |
| struct Z : Y { int z; constexpr ~Z() override { *p++ = 'Z'; } }; |
| union VU { |
| constexpr VU() : z() {} |
| constexpr ~VU() {} |
| Z z; |
| }; |
| |
| constexpr bool virt_dtor(int mode, const char *expected) { |
| char buff[4] = {}; |
| VU vu; |
| vu.z.p = buff; |
| switch (mode) { |
| case 0: |
| vu.z.~Z(); |
| break; |
| case 1: |
| ((Y&)vu.z).~Y(); |
| break; |
| case 2: |
| ((X&)vu.z).~X(); |
| break; |
| case 3: |
| ((Y&)vu.z).Y::~Y(); |
| vu.z.z = 1; // ok, still have a Z (with no Y base class!) |
| break; |
| case 4: |
| ((X&)vu.z).X::~X(); |
| vu.z.y = 1; // ok, still have a Z and a Y (with no X base class!) |
| break; |
| } |
| return __builtin_strcmp(expected, buff) == 0; |
| } |
| static_assert(virt_dtor(0, "ZYX")); |
| static_assert(virt_dtor(1, "ZYX")); |
| static_assert(virt_dtor(2, "X")); |
| static_assert(virt_dtor(3, "YX")); |
| static_assert(virt_dtor(4, "X")); |
| |
| constexpr bool virt_delete(bool global) { |
| struct A { |
| virtual constexpr ~A() {} |
| }; |
| struct B : A { |
| void operator delete(void *); |
| constexpr ~B() {} |
| }; |
| |
| A *p = new B; |
| if (global) |
| ::delete p; |
| else |
| delete p; // expected-note {{call to class-specific 'operator delete'}} |
| return true; |
| } |
| static_assert(virt_delete(true)); |
| static_assert(virt_delete(false)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void use_after_virt_destroy() { |
| char buff[4] = {}; |
| VU vu; |
| vu.z.p = buff; |
| ((Y&)vu.z).~Y(); |
| ((Z&)vu.z).z = 1; // expected-note {{assignment to object outside its lifetime}} |
| } |
| static_assert((use_after_virt_destroy(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void destroy_after_lifetime() { |
| A *p; |
| { |
| A a; |
| p = &a; |
| } |
| p->~A(); // expected-note {{destruction of object outside its lifetime}} |
| } |
| static_assert((destroy_after_lifetime(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void destroy_after_lifetime2() { |
| A *p = []{ A a; return &a; }(); // expected-warning {{}} expected-note {{declared here}} |
| p->~A(); // expected-note {{destruction of variable whose lifetime has ended}} |
| } |
| static_assert((destroy_after_lifetime2(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void destroy_after_lifetime3() { |
| A *p = []{ return &(A&)(A&&)A(); }(); // expected-warning {{}} expected-note {{temporary created here}} |
| p->~A(); // expected-note {{destruction of temporary whose lifetime has ended}} |
| } |
| static_assert((destroy_after_lifetime3(), true)); // expected-error {{}} expected-note {{in call}} |
| |
| constexpr void destroy_after_lifetime4() { // expected-error {{never produces a constant expression}} |
| A *p = new A; |
| delete p; |
| p->~A(); // expected-note {{destruction of heap allocated object that has been deleted}} |
| } |
| |
| struct Extern { constexpr ~Extern() {} } extern e; |
| constexpr void destroy_extern() { // expected-error {{never produces a constant expression}} |
| e.~Extern(); // expected-note {{cannot modify an object that is visible outside}} |
| } |
| |
| constexpr A &&a_ref = A(); // expected-note {{temporary created here}} |
| constexpr void destroy_extern_2() { // expected-error {{never produces a constant expression}} |
| a_ref.~A(); // expected-note {{destruction of temporary is not allowed in a constant expression outside the expression that created the temporary}} |
| } |
| |
| struct S { |
| constexpr S() { n = 1; } |
| constexpr ~S() { n = 0; } |
| int n; |
| }; |
| constexpr void destroy_volatile() { |
| volatile S s; |
| } |
| static_assert((destroy_volatile(), true)); // ok, not volatile during construction and destruction |
| |
| constexpr void destroy_null() { // expected-error {{never produces a constant expression}} |
| ((A*)nullptr)->~A(); // expected-note {{destruction of dereferenced null pointer}} |
| } |
| |
| constexpr void destroy_past_end() { // expected-error {{never produces a constant expression}} |
| A a; |
| (&a+1)->~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}} |
| } |
| |
| constexpr void destroy_past_end_array() { // expected-error {{never produces a constant expression}} |
| A a[2]; |
| a[2].~A(); // expected-note {{destruction of dereferenced one-past-the-end pointer}} |
| } |
| |
| union As { |
| A a, b; |
| }; |
| |
| constexpr void destroy_no_active() { // expected-error {{never produces a constant expression}} |
| As as; |
| as.b.~A(); // expected-note {{destruction of member 'b' of union with no active member}} |
| } |
| |
| constexpr void destroy_inactive() { // expected-error {{never produces a constant expression}} |
| As as; |
| as.a.n = 1; |
| as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}} |
| } |
| |
| constexpr void destroy_no_active_2() { // expected-error {{never produces a constant expression}} |
| As as; |
| as.a.n = 1; |
| as.a.~A(); |
| // FIXME: This diagnostic is wrong; the union has no active member now. |
| as.b.~A(); // expected-note {{destruction of member 'b' of union with active member 'a'}} |
| } |
| |
| constexpr void destroy_pointer() { |
| using T = int*; |
| T p; |
| // We used to think this was an -> member access because its left-hand side |
| // is a pointer. Ensure we don't crash. |
| p.~T(); |
| } |
| static_assert((destroy_pointer(), true)); |
| } |
| |
| namespace temp_dtor { |
| void f(); |
| struct A { |
| bool b; |
| constexpr ~A() { if (b) f(); } |
| }; |
| |
| // We can't accept either of these unless we start actually registering the |
| // destructors of the A temporaries to run on shutdown. It's unclear what the |
| // intended standard behavior is so we reject this for now. |
| constexpr A &&a = A{false}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}} |
| void f() { a.b = true; } |
| |
| constexpr A &&b = A{true}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}} |
| |
| // FIXME: We could in prinicple accept this. |
| constexpr const A &c = A{false}; // expected-error {{constant}} expected-note {{non-trivial destruction of lifetime-extended temporary}} |
| } |
| |
| namespace value_dependent_init { |
| struct A { |
| constexpr ~A() {} |
| }; |
| template<typename T> void f() { |
| A a = T(); |
| } |
| } |