aaron.ballman created this revision. aaron.ballman added reviewers: rsmith, erichkeane, clang-language-wg, jyknight. Herald added a project: All. aaron.ballman requested review of this revision. Herald added a project: clang.
When instantiating a class template, if the primary class template special member function declaration was declared `constexpr` or `consteval`, the instantiation is also considered to be a consteval function even if the instantiation would not be valid to use in a constant expression (http://eel.is/c++draft/dcl.constexpr#7). However, when instantiating such a class, we were incorrectly saying the special member was not a constexpr function when a base class or member variable would cause the instantiation to not be usable in a constant expression. This addresses the issue by falling back to the primary declaration of the explicitly defaulted special member function. This caused some unexpected test failures with friend declaration checking, but the standard is quite unclear on what's expected there. We also have existing bugs in that particular area (https://godbolt.org/z/cPKGvx746). I raised some questions on the Core reflectors about what is expected and put a FIXME into the test to make it clear that's unresolved. Repository: rG LLVM Github Monorepo https://reviews.llvm.org/D128083 Files: clang/docs/ReleaseNotes.rst clang/lib/AST/DeclCXX.cpp clang/lib/Sema/SemaDeclCXX.cpp clang/test/SemaCXX/cxx0x-defaulted-functions.cpp clang/test/SemaCXX/cxx2a-consteval.cpp
Index: clang/test/SemaCXX/cxx2a-consteval.cpp =================================================================== --- clang/test/SemaCXX/cxx2a-consteval.cpp +++ clang/test/SemaCXX/cxx2a-consteval.cpp @@ -766,3 +766,55 @@ static_assert(c == 8); } } + +namespace DefaultedTemp { +template <typename T> +struct default_ctor { + T data; + consteval default_ctor() = default; // expected-note {{non-constexpr constructor 'foo' cannot be used in a constant expression}} +}; + +template <typename T> +struct copy { + T data; + + consteval copy(const copy &) = default; // expected-note {{non-constexpr constructor 'foo' cannot be used in a constant expression}} + consteval copy &operator=(const copy &) = default; // expected-note {{non-constexpr function 'operator=' cannot be used in a constant expression}} + copy() = default; +}; + +template <typename T> +struct move { + T data; + + consteval move(move &&) = default; // expected-note {{non-constexpr constructor 'foo' cannot be used in a constant expression}} + consteval move &operator=(move &&) = default; // expected-note {{non-constexpr function 'operator=' cannot be used in a constant expression}} + move() = default; +}; + +struct foo { + foo() {} // expected-note {{declared here}} + foo(const foo &) {} // expected-note {{declared here}} + foo(foo &&) {} // expected-note {{declared here}} + + foo& operator=(const foo &) { return *this; } // expected-note {{declared here}} + foo& operator=(foo &&) { return *this; } // expected-note {{declared here}} +}; + +void func() { + default_ctor<foo> fail0; // expected-error {{call to consteval function 'DefaultedTemp::default_ctor<DefaultedTemp::foo>::default_ctor' is not a constant expression}} \ + expected-note {{in call to 'default_ctor()'}} + + copy<foo> good0; + copy<foo> fail1{good0}; // expected-error {{call to consteval function 'DefaultedTemp::copy<DefaultedTemp::foo>::copy' is not a constant expression}} \ + expected-note {{in call to 'copy(good0)'}} + fail1 = good0; // expected-error {{call to consteval function 'DefaultedTemp::copy<DefaultedTemp::foo>::operator=' is not a constant expression}} \ + expected-note {{in call to '&fail1->operator=(good0)'}} + + move<foo> good1; + move<foo> fail2{static_cast<move<foo>&&>(good1)}; // expected-error {{call to consteval function 'DefaultedTemp::move<DefaultedTemp::foo>::move' is not a constant expression}} \ + expected-note {{in call to 'move(good1)'}} + fail2 = static_cast<move<foo>&&>(good1); // expected-error {{call to consteval function 'DefaultedTemp::move<DefaultedTemp::foo>::operator=' is not a constant expression}} \ + expected-note {{in call to '&fail2->operator=(good1)'}} +} +} // namespace DefaultedTemp Index: clang/test/SemaCXX/cxx0x-defaulted-functions.cpp =================================================================== --- clang/test/SemaCXX/cxx0x-defaulted-functions.cpp +++ clang/test/SemaCXX/cxx0x-defaulted-functions.cpp @@ -44,18 +44,20 @@ } template<typename T> struct S : T { - constexpr S() = default; - constexpr S(const S&) = default; - constexpr S(S&&) = default; + constexpr S() = default; // expected-note {{previous declaration is here}} + constexpr S(const S&) = default; // expected-note {{previous declaration is here}} + constexpr S(S&&) = default; // expected-note {{previous declaration is here}} }; struct lit { constexpr lit() {} }; S<lit> s_lit; // ok S<bar> s_bar; // ok struct Friends { - friend S<bar>::S(); - friend S<bar>::S(const S&); - friend S<bar>::S(S&&); + // FIXME: these error may or may not be correct; there is an open question on + // the CWG reflectors about this. + friend S<bar>::S(); // expected-error {{non-constexpr declaration of 'S' follows constexpr declaration}} + friend S<bar>::S(const S&); // expected-error {{non-constexpr declaration of 'S' follows constexpr declaration}} + friend S<bar>::S(S&&); // expected-error {{non-constexpr declaration of 'S' follows constexpr declaration}} }; namespace DefaultedFnExceptionSpec { Index: clang/lib/Sema/SemaDeclCXX.cpp =================================================================== --- clang/lib/Sema/SemaDeclCXX.cpp +++ clang/lib/Sema/SemaDeclCXX.cpp @@ -7544,6 +7544,17 @@ // FIXME: This should not apply if the member is deleted. bool Constexpr = defaultedSpecialMemberIsConstexpr(*this, RD, CSM, HasConstParam); + + // C++14 [dcl.constexpr]p6 (CWG DR647/CWG DR1358): + // If the instantiated template specialization of a constexpr function + // template or member function of a class template would fail to satisfy + // the requirements for a constexpr function or constexpr constructor, that + // specialization is still a constexpr function or constexpr constructor, + // even though a call to such a function cannot appear in a constant + // expression. + if (!Constexpr && isa<ClassTemplateSpecializationDecl>(RD)) + Constexpr = MD->isConstexpr(); + if ((getLangOpts().CPlusPlus20 || (getLangOpts().CPlusPlus14 ? !isa<CXXDestructorDecl>(MD) : isa<CXXConstructorDecl>(MD))) && Index: clang/lib/AST/DeclCXX.cpp =================================================================== --- clang/lib/AST/DeclCXX.cpp +++ clang/lib/AST/DeclCXX.cpp @@ -1303,11 +1303,21 @@ // C++11 [dcl.constexpr]p4: // -- every constructor involved in initializing non-static data // members [...] shall be a constexpr constructor + // C++14 [dcl.constexpr]p6 (CWG DR647/CWG DR1358): + // If the instantiated template specialization of a constexpr + // function template or member function of a class template would + // fail to satisfy the requirements for a constexpr function or + // constexpr constructor, that specialization is still a constexpr + // function or constexpr constructor, even though a call to such a + // function cannot appear in a constant expression. if (!Field->hasInClassInitializer() && !FieldRec->hasConstexprDefaultConstructor() && !isUnion()) // The standard requires any in-class initializer to be a constant // expression. We consider this to be a defect. - data().DefaultedDefaultConstructorIsConstexpr = false; + data().DefaultedDefaultConstructorIsConstexpr = + isa<ClassTemplateSpecializationDecl>(this) + ? hasConstexprDefaultConstructor() + : false; // C++11 [class.copy]p8: // The implicitly-declared copy constructor for a class X will have Index: clang/docs/ReleaseNotes.rst =================================================================== --- clang/docs/ReleaseNotes.rst +++ clang/docs/ReleaseNotes.rst @@ -435,7 +435,10 @@ - No longer attempt to evaluate a consteval UDL function call at runtime when it is called through a template instantiation. This fixes `Issue 54578 <https://github.com/llvm/llvm-project/issues/54578>`_. - +- Properly determine whether an instantiated, explicitly defaulted constexpr or + consteval special member function is still considered to be constexpr or + consteval instead of calculating it purely from the bases and fields of the + class being instantiated. - Implemented ``__builtin_source_location()``, which enables library support for ``std::source_location``.
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org https://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits