r261297 - Implement the likely resolution of core issue 253.

Nico Weber via cfe-commits cfe-commits at lists.llvm.org
Wed Feb 24 10:47:50 PST 2016


Thanks for patiently explaining this. The attached patch is your email in
diff form. Does this look alright?

Since you mention C++98: We emit this diagnostic in C++98 mode (before and
after my change). The rule is new in C+++11, right? Should I add a check
for CPlusPlus11 before emitting this diagnostic (in a separate change)?

I first forgot to undo my isDefaultCtor() change, and all the tests pass
both with and without it. Can you think of a test case that fails with the
isDefaultCtor() patch? (The new tests fail with _just_ the isDefaultCtor()
change.)

On Tue, Feb 23, 2016 at 2:41 PM, Richard Smith <richard at metafoo.co.uk>
wrote:

> On Sat, Feb 20, 2016 at 6:53 AM, Nico Weber <thakis at chromium.org> wrote:
> > On Fri, Feb 19, 2016 at 10:32 PM, Nico Weber <thakis at chromium.org>
> wrote:
> >>
> >> On Fri, Feb 19, 2016 at 10:02 PM, Nico Weber <thakis at chromium.org>
> wrote:
> >>>
> >>> On Fri, Feb 19, 2016 at 8:01 PM, Richard Smith <richard at metafoo.co.uk>
> >>> wrote:
> >>>>
> >>>> On Fri, Feb 19, 2016 at 4:41 PM, Nico Weber <thakis at chromium.org>
> wrote:
> >>>> > On Fri, Feb 19, 2016 at 4:29 PM, Richard Smith via cfe-commits
> >>>> > <cfe-commits at lists.llvm.org> wrote:
> >>>> >>
> >>>> >> On Thu, Feb 18, 2016 at 5:52 PM, Nico Weber via cfe-commits
> >>>> >> <cfe-commits at lists.llvm.org> wrote:
> >>>> >> > Author: nico
> >>>> >> > Date: Thu Feb 18 19:52:46 2016
> >>>> >> > New Revision: 261297
> >>>> >> >
> >>>> >> > URL: http://llvm.org/viewvc/llvm-project?rev=261297&view=rev
> >>>> >> > Log:
> >>>> >> > Implement the likely resolution of core issue 253.
> >>>> >> >
> >>>> >> > C++11 requires const objects to have a user-provided constructor,
> >>>> >> > even
> >>>> >> > for
> >>>> >> > classes without any fields. DR 253 relaxes this to say "If the
> >>>> >> > implicit
> >>>> >> > default
> >>>> >> > constructor initializes all subobjects, no initializer should be
> >>>> >> > required."
> >>>> >> >
> >>>> >> > clang is currently the only compiler that implements this C++11
> >>>> >> > rule,
> >>>> >> > and e.g.
> >>>> >> > libstdc++ relies on something like DR 253 to compile in newer
> >>>> >> > versions.
> >>>> >> > This
> >>>> >> > change  makes it possible to build code that says `const
> >>>> >> > vector<int> v;'
> >>>> >> > again
> >>>> >> > when using libstdc++5.2 and _GLIBCXX_DEBUG
> >>>> >> > (https://gcc.gnu.org/bugzilla/show_bug.cgi?id=60284).
> >>>> >> >
> >>>> >> > Fixes PR23381.
> >>>> >> >
> >>>> >> > http://reviews.llvm.org/D16552
> >>>> >> >
> >>>> >> > Modified:
> >>>> >> >     cfe/trunk/include/clang/AST/DeclCXX.h
> >>>> >> >     cfe/trunk/lib/AST/ASTImporter.cpp
> >>>> >> >     cfe/trunk/lib/AST/DeclCXX.cpp
> >>>> >> >     cfe/trunk/lib/Sema/SemaInit.cpp
> >>>> >> >     cfe/trunk/lib/Serialization/ASTReaderDecl.cpp
> >>>> >> >     cfe/trunk/lib/Serialization/ASTWriter.cpp
> >>>> >> >
> >>>> >> >
> cfe/trunk/test/CXX/dcl.decl/dcl.fct.def/dcl.fct.def.default/p2.cpp
> >>>> >> >     cfe/trunk/test/CXX/dcl.decl/dcl.init/p6.cpp
> >>>> >> >     cfe/trunk/test/CXX/drs/dr4xx.cpp
> >>>> >> >     cfe/trunk/test/SemaCXX/attr-selectany.cpp
> >>>> >> >     cfe/trunk/test/SemaCXX/constexpr-value-init.cpp
> >>>> >> >     cfe/trunk/test/SemaCXX/cxx0x-cursory-default-delete.cpp
> >>>> >> >     cfe/trunk/test/SemaCXX/illegal-member-initialization.cpp
> >>>> >> >     cfe/trunk/www/cxx_dr_status.html
> >>>> >> >
> >>>> >> > Modified: cfe/trunk/include/clang/AST/DeclCXX.h
> >>>> >> > URL:
> >>>> >> >
> >>>> >> >
> http://llvm.org/viewvc/llvm-project/cfe/trunk/include/clang/AST/DeclCXX.h?rev=261297&r1=261296&r2=261297&view=diff
> >>>> >> >
> >>>> >> >
> >>>> >> >
> ==============================================================================
> >>>> >> > --- cfe/trunk/include/clang/AST/DeclCXX.h (original)
> >>>> >> > +++ cfe/trunk/include/clang/AST/DeclCXX.h Thu Feb 18 19:52:46
> 2016
> >>>> >> > @@ -378,6 +378,10 @@ class CXXRecordDecl : public RecordDecl
> >>>> >> >      /// even if the class has a trivial default constructor.
> >>>> >> >      bool HasUninitializedReferenceMember : 1;
> >>>> >> >
> >>>> >> > +    /// \brief True if any non-mutable field whose type doesn't
> >>>> >> > have a
> >>>> >> > user-
> >>>> >> > +    /// provided default ctor also doesn't have an in-class
> >>>> >> > initializer.
> >>>> >> > +    bool HasUninitializedFields : 1;
> >>>> >> > +
> >>>> >> >      /// \brief These flags are \c true if a defaulted
> >>>> >> > corresponding
> >>>> >> > special
> >>>> >> >      /// member can't be fully analyzed without performing
> overload
> >>>> >> > resolution.
> >>>> >> >      /// @{
> >>>> >> > @@ -1270,6 +1274,13 @@ public:
> >>>> >> >      return !(data().HasTrivialSpecialMembers & SMF_Destructor);
> >>>> >> >    }
> >>>> >> >
> >>>> >> > +  /// \brief Determine whether declaring a const variable with
> >>>> >> > this
> >>>> >> > type is ok
> >>>> >> > +  /// per core issue 253.
> >>>> >> > +  bool allowConstDefaultInit() const {
> >>>> >> > +    return !data().HasUninitializedFields ||
> >>>> >> > +           hasUserProvidedDefaultConstructor();
> >>>> >>
> >>>> >> hasUserProvidedDefaultConstructor() here is subtly incorrect. We
> >>>> >> shouldn't care whether there's a user-provided default constructor,
> >>>> >> we
> >>>> >> instead care whether the constructor that would have been chosen
> for
> >>>> >> initialization is defaulted (or equivalently, whether there *is* a
> >>>> >> defaulted default constructor, since if there is one, then either
> the
> >>>> >> initialization is ambiguous or it is chosen).
> >>>> >>
> >>>> >> This causes a regression for a testcase such as:
> >>>> >>
> >>>> >> struct X { template<typename ...T> X(T...); int n; };
> >>>> >> const X x; // formerly OK, now bogus error
> >>>> >
> >>>> >
> >>>> > Hm, great point. For a single hop, this fixes it:
> >>>> >
> >>>> > Index: lib/Sema/SemaInit.cpp
> >>>> > ===================================================================
> >>>> > --- lib/Sema/SemaInit.cpp (revision 261298)
> >>>> > +++ lib/Sema/SemaInit.cpp (working copy)
> >>>> > @@ -3521,7 +3521,7 @@
> >>>> >    // The 253 proposal is for example needed to process libstdc++
> >>>> > headers in
> >>>> > 5.x.
> >>>> >    CXXConstructorDecl *CtorDecl =
> >>>> > cast<CXXConstructorDecl>(Best->Function);
> >>>> >    if (Kind.getKind() == InitializationKind::IK_Default &&
> >>>> > -      Entity.getType().isConstQualified()) {
> >>>> > +      Entity.getType().isConstQualified() &&
> >>>> > !CtorDecl->isUserProvided()) {
> >>>> >      if (!CtorDecl->getParent()->allowConstDefaultInit()) {
> >>>> >        if (!maybeRecoverWithZeroInitialization(S, Sequence, Entity))
> >>>> >
> >>>> > Sequence.SetFailed(InitializationSequence::FK_DefaultInitOfConst);
> >>>> >
> >>>> >
> >>>> > However, it doesn't make this pass:
> >>>> >
> >>>> > struct X { template<typename ...T> X(T...); int n; };
> >>>> > struct Y { X x; };
> >>>> > const Y y;
> >>>> >
> >>>> > That didn't build before this change either, but it feels like this
> >>>> > should
> >>>> > be ok after this change. I think what you're suggesting is to
> >>>> > conceptually
> >>>> > do this instead:
> >>>> >
> >>>> > Index: include/clang/AST/DeclCXX.h
> >>>> > ===================================================================
> >>>> > --- include/clang/AST/DeclCXX.h (revision 261298)
> >>>> > +++ include/clang/AST/DeclCXX.h (working copy)
> >>>> > @@ -1277,8 +1277,10 @@
> >>>> >    /// \brief Determine whether declaring a const variable with this
> >>>> > type is
> >>>> > ok
> >>>> >    /// per core issue 253.
> >>>> >    bool allowConstDefaultInit() const {
> >>>> > -    return !data().HasUninitializedFields ||
> >>>> > -           hasUserProvidedDefaultConstructor();
> >>>> > +    if (!data().HasUninitializedFields)
> >>>> > +      return true;
> >>>> > +    CXXConstructorDecl *CD = S.LookupDefaultConstructor(ClassDecl);
> >>>> > +    return !CD->isDefaulted();
> >>>> >    }
> >>>> >
> >>>> >    /// \brief Determine whether this class has a destructor which
> has
> >>>> > no
> >>>> >
> >>>> > Now AST can't access Sema of course, so one way to do this would be
> to
> >>>> > look
> >>>> > up the default ctor for every record in sema when
> completeDefinition()
> >>>> > for a
> >>>> > record is called and then do
> >>>> >
> >>>> >   if (!CD->isDefaulted())
> >>>> >     RD->setAllowConstDefaultInit(true);
> >>>> >
> >>>> > But looking up the constructor is a bit more expensive than the
> >>>> > current
> >>>> > computation, so maybe it makes sense to go back to lazy computation
> of
> >>>> > this
> >>>> > information? Do you have any preferences for how to implement this?
> >>>>
> >>>> We don't need to actually do overload resolution here. There are three
> >>>> cases:
> >>>>
> >>>> 0) Default initialization is ill-formed in some way => we don't care
> >>>> what this function returns
> >>>> 1) There is no defaulted default constructor => const default init is
> OK
> >>>> 2) There is a defaulted default constructor => default init must use
> >>>> it (any alternative would put us in case 0), const default init is not
> >>>> OK if we have uninitialized fields
> >>>>
> >>>> So we only need to know if there is either an implicit default
> >>>> constructor,
> >>>
> >>>
> >>> How would you know that in your example though?
>
> When a constructor is added to the class, we can check whether it's a
> defaulted default constructor (that is, add another flag to
> DefinitionData to track whether there is one)[1]. Then the class has a
> defaulted default constructor if either (a) you've seen one, or (b)
> the class needs one to be implicitly declared (we already have a flag
> for that on CXXRecordDecl).
>
> So, we allow default const init if
> !(HasDeclaredDefaultedDefaultConstructor ||
> needsImplicitDefaultConstructor()) || !HasUninitializedFields.
>
>  [1]: I'm not sure whether we set an implicit default constructor to
> be defaulted in C++98 mode. If not, you can check whether it's
> defaulted or implicit.
>
> >>> Actually, after reading
> >>> the code a bit more, how about this instead:
> >>>
> >>> Index: lib/AST/DeclCXX.cpp
> >>> ===================================================================
> >>> --- lib/AST/DeclCXX.cpp (revision 261301)
> >>> +++ lib/AST/DeclCXX.cpp (working copy)
> >>> @@ -1793,7 +1803,8 @@
> >>>    //   A default constructor for a class X is a constructor of class
> >>>    //   X that can be called without an argument.
> >>>    return (getNumParams() == 0) ||
> >>> -         (getNumParams() > 0 && getParamDecl(0)->hasDefaultArg());
> >>> +         (getNumParams() > 0 && getParamDecl(0)->hasDefaultArg()) ||
> >>> +         (getNumParams() == 1 && getParamDecl(0)->isParameterPack());
> >>>  }
> >>>
> >>>  bool
> >>>
> >>> Fixes the test cases, passes the test suite, and seems like a good
> change
> >>> to me. For example, in
> >>>
> >>> #include <type_traits>
> >>> struct X {
> >>>   template <typename... T,
> >>>             typename = typename std::enable_if<sizeof...(T) !=
> 0>::type>
> >>>   X(T...);
> >>>   int n;
> >>> };
> >>> struct Y { X x; };
> >>> const Y y;
> >>>
> >>> the explicit parameter pack deletes the implicit default ctor even
> though
> >>> it's SFINAE'd out. (I tried to find a different example where this
> change
> >>> makes an observable difference but so far I've failed to find one. This
> >>> probably impacts other things though.)
> >>
> >>
> >> After looking through the callers of isDefaultConstructor(), maybe it
> >> actually doesn't affect other things. It does make it possible to fix
> the
> >> following example with a another small tweak:
> >>
> >> struct X { template<typename ...T> constexpr X(T...) noexcept {} };
> >> static_assert(__has_nothrow_constructor(X), "");
> >>
> >> (clang currently rejects this, but gcc accepts it, and it looks like it
> >> ought to be accepted.)
> >
> >
> > I think I like the approach of viewing this as a bug of isDefaultCtor().
> > Here's a patch that adds a few test cases and that also handles multiple
> > parameter packs followed by default arguments correctly. Please take a
> look.
>
> That change is not correct :( Consider a class like this:
>
> struct X {
>   template<typename ...T, typename enable_if<sizeof...(T) != 0>::type*
> = nullptr> X(T...);
>   X() = default;
> };
>
> Here, the constructor template is not a default constructor. This
> class has only one default constructor, and it is defaulted.
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.llvm.org/pipermail/cfe-commits/attachments/20160224/10025055/attachment-0001.html>
-------------- next part --------------
Index: lib/AST/ASTImporter.cpp
===================================================================
--- lib/AST/ASTImporter.cpp	(revision 261674)
+++ lib/AST/ASTImporter.cpp	(working copy)
@@ -2040,6 +2040,8 @@
     ToData.HasIrrelevantDestructor = FromData.HasIrrelevantDestructor;
     ToData.HasConstexprNonCopyMoveConstructor
       = FromData.HasConstexprNonCopyMoveConstructor;
+    ToData.HasDefaultedDefaultConstructor
+      = FromData.HasDefaultedDefaultConstructor;
     ToData.DefaultedDefaultConstructorIsConstexpr
       = FromData.DefaultedDefaultConstructorIsConstexpr;
     ToData.HasConstexprDefaultConstructor
Index: lib/AST/DeclCXX.cpp
===================================================================
--- lib/AST/DeclCXX.cpp	(revision 261674)
+++ lib/AST/DeclCXX.cpp	(working copy)
@@ -61,6 +61,7 @@
       DefaultedDestructorIsDeleted(false), HasTrivialSpecialMembers(SMF_All),
       DeclaredNonTrivialSpecialMembers(0), HasIrrelevantDestructor(true),
       HasConstexprNonCopyMoveConstructor(false),
+      HasDefaultedDefaultConstructor(false),
       DefaultedDefaultConstructorIsConstexpr(true),
       HasConstexprDefaultConstructor(false),
       HasNonLiteralTypeFieldsOrBases(false), ComputedVisibleConversions(false),
@@ -497,6 +498,8 @@
         data().UserProvidedDefaultConstructor = true;
       if (Constructor->isConstexpr())
         data().HasConstexprDefaultConstructor = true;
+      if (Constructor->isDefaulted())
+        data().HasDefaultedDefaultConstructor = true;
     }
 
     if (!FunTmpl) {
Index: include/clang/AST/DeclCXX.h
===================================================================
--- include/clang/AST/DeclCXX.h	(revision 261674)
+++ include/clang/AST/DeclCXX.h	(working copy)
@@ -421,6 +421,10 @@
     /// constructor which is neither the copy nor move constructor.
     bool HasConstexprNonCopyMoveConstructor : 1;
 
+    /// \brief True if this class has a (possibly implicit) defaulted default
+    /// constructor.
+    bool HasDefaultedDefaultConstructor : 1;
+
     /// \brief True if a defaulted default constructor for this class would
     /// be constexpr.
     bool DefaultedDefaultConstructorIsConstexpr : 1;
@@ -1278,7 +1282,8 @@
   /// per core issue 253.
   bool allowConstDefaultInit() const {
     return !data().HasUninitializedFields ||
-           hasUserProvidedDefaultConstructor();
+           !(data().HasDefaultedDefaultConstructor ||
+             needsImplicitDefaultConstructor());
   }
 
   /// \brief Determine whether this class has a destructor which has no
Index: lib/Serialization/ASTWriter.cpp
===================================================================
--- lib/Serialization/ASTWriter.cpp	(revision 261674)
+++ lib/Serialization/ASTWriter.cpp	(working copy)
@@ -5558,6 +5558,7 @@
   Record.push_back(Data.DeclaredNonTrivialSpecialMembers);
   Record.push_back(Data.HasIrrelevantDestructor);
   Record.push_back(Data.HasConstexprNonCopyMoveConstructor);
+  Record.push_back(Data.HasDefaultedDefaultConstructor);
   Record.push_back(Data.DefaultedDefaultConstructorIsConstexpr);
   Record.push_back(Data.HasConstexprDefaultConstructor);
   Record.push_back(Data.HasNonLiteralTypeFieldsOrBases);
Index: lib/Serialization/ASTReaderDecl.cpp
===================================================================
--- lib/Serialization/ASTReaderDecl.cpp	(revision 261674)
+++ lib/Serialization/ASTReaderDecl.cpp	(working copy)
@@ -1423,6 +1423,7 @@
   Data.DeclaredNonTrivialSpecialMembers = Record[Idx++];
   Data.HasIrrelevantDestructor = Record[Idx++];
   Data.HasConstexprNonCopyMoveConstructor = Record[Idx++];
+  Data.HasDefaultedDefaultConstructor = Record[Idx++];
   Data.DefaultedDefaultConstructorIsConstexpr = Record[Idx++];
   Data.HasConstexprDefaultConstructor = Record[Idx++];
   Data.HasNonLiteralTypeFieldsOrBases = Record[Idx++];
@@ -1548,6 +1549,7 @@
   OR_FIELD(DeclaredNonTrivialSpecialMembers)
   MATCH_FIELD(HasIrrelevantDestructor)
   OR_FIELD(HasConstexprNonCopyMoveConstructor)
+  OR_FIELD(HasDefaultedDefaultConstructor)
   MATCH_FIELD(DefaultedDefaultConstructorIsConstexpr)
   OR_FIELD(HasConstexprDefaultConstructor)
   MATCH_FIELD(HasNonLiteralTypeFieldsOrBases)
Index: test/SemaCXX/cxx0x-cursory-default-delete.cpp
===================================================================
--- test/SemaCXX/cxx0x-cursory-default-delete.cpp	(revision 261674)
+++ test/SemaCXX/cxx0x-cursory-default-delete.cpp	(working copy)
@@ -74,7 +74,35 @@
 struct no_fields_container {
   no_fields nf;
 };
+struct param_pack_ctor {
+  template <typename... T>
+  param_pack_ctor(T...);
+  int n;
+};
+struct param_pack_ctor_field {
+  param_pack_ctor ndc;
+};
+struct multi_param_pack_ctor {
+  template <typename... T, typename... U>
+  multi_param_pack_ctor(T..., U..., int f = 0);
+  int n;
+};
+struct ignored_template_ctor_and_def {
+  template <class T> ignored_template_ctor_and_def(T* f = nullptr);
+  ignored_template_ctor_and_def() = default;
+  int field;
+};
 
+template<bool, typename = void> struct enable_if {};
+template<typename T> struct enable_if<true, T> { typedef T type; };
+struct multi_param_pack_and_defaulted {
+  template <typename... T,
+            typename enable_if<sizeof...(T) != 0>::type* = nullptr>
+  multi_param_pack_and_defaulted(T...);
+  multi_param_pack_and_defaulted() = default;
+  int n;
+};
+
 void constobjs() {
   const no_fields nf; // ok
   const all_init ai; // ok
@@ -88,6 +116,12 @@
   const some_init_container sicon; // expected-error {{default initialization of an object of const type 'const some_init_container' without a user-provided default constructor}}
   const some_init_container_ctor siconc; // ok
   const no_fields_container nfc; // ok
+  const param_pack_ctor ppc; // ok
+  const param_pack_ctor_field ppcf; // ok
+  const multi_param_pack_ctor mppc; // ok
+  const multi_param_pack_and_defaulted mppad; // expected-error {{default initialization of an object of const type 'const multi_param_pack_and_defaulted' without a user-provided default constructor}}
+  const ignored_template_ctor_and_def itcad; // expected-error {{default initialization of an object of const type 'const ignored_template_ctor_and_def' without a user-provided default constructor}}
+
 }
 
 struct non_const_derived : non_const_copy {


More information about the cfe-commits mailing list