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
  • [PATCH] D128083: ... Aaron Ballman via Phabricator via cfe-commits

Reply via email to