The following compiles with MSVC2017 and not with gcc8.2. This bug was reported first on stackoverflow (https://stackoverflow.com/questions/53408962/try-to-understand-compiler-error-message-default-member-initializer-required-be) minimum code example: -prog.cc------------------------- #include <limits> class A { public: class B { public: explicit B() = default; ~B() = default; private: double m = std::numeric_limits<double>::max(); }; void f(double d, const B &b = B{}) {} }; int main() { A a{}; a.f(0.); } -------------------------------- compile command: g++ prog.cc -std=c++17 -------------------------------- error message: prog.cc:15:36: error: default member initializer for 'A::B::m' required before the end of its enclosing class void f(double d, const B &b = B{}) {} ^ prog.cc:12:22: note: defined here double m = std::numeric_limits<double>::max(); ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
minim code: --------------- struct A { struct B { double m = 0.; }; void f(double d, const B &b = B{}) {} }; int main() { } ---------------
Any update on this?
As of now, this code is still rejected.
Maybe we need more delayed parsing here.
Hmm, clang also rejects this ...
DR 1352 might be related.
This struct definition: ``` struct A { struct B { int i = 0; B() {} }; A(B = {}); }; ``` is accepted by GCC, but another one ({} replaced with = default) is not: ``` struct C { struct D { int i = 0; D() = default; }; C(D = {}); }; ``` Demo: https://gcc.godbolt.org/z/WTPdTn1Yf Could you please explain why? I though that both must be same accepted or same rejected.
Also suffering from this issue, it's really annoying and diverged from msvc...
This is PR 96645 and the examples on comment 1 and comment 7 compile with GCC trunk. The example in comment 0 doesn't compile still, see PR 96645 for details. *** This bug has been marked as a duplicate of bug 96645 ***
(In reply to Fedor Chelnokov from comment #7) > This struct definition: > ``` > struct A { > struct B { > int i = 0; > B() {} This declares default constructor, meaning that the type is default constructible. Period. By declaring B(); you assert that B is default constructible. > }; > A(B = {}); This is valid, because the user-provided default constructor for B is all the compiler needs to see to know that B={} is valid. > }; > ``` > is accepted by GCC, but another one ({} replaced with = default) is not: > ``` > struct C { > struct D { > int i = 0; > D() = default; This declares a default constructor that might be defined implicitly by the compiler, **or** it might get deleted if the member definitions of D would make the implicit default constructor ill-formed. This is obviously very different from the case where you declare B(); There is no assertion that D is default constructible, the compiler has to deduce whether or not that's true. That depends on the default member initializer for D::i. The initializer (which is just '0' here) is a "complete class context" which means it is not processed until the class D is complete (this allows you to use other members, or e.g. sizeof(D) as the initializer). A nested class like C::D is not complete until its enclosing class is complete. This means the initializer for C::D::i is compiled after C is complete. This means whether C::D is default constructible is not known until C is complete. > }; > C(D = {}); This requires checking whether C::D is default constructible, but we are still in the body of C, so C is not complete, which means C::D is not complete, which means we don't know if C::D is default constructible. > }; > ``` > Demo: https://gcc.godbolt.org/z/WTPdTn1Yf > > Could you please explain why? I though that both must be same accepted or > same rejected. I hope the explanation above helps. GCC trunk now has a tweak to parse simple initializers like 0 immediately, instead of waiting for the class to be complete (because 0 doesn't depend on anything in the class). But for the original example in comment 0 the initializer std::numeric_limits<double>::max(); has to perform name lookup and overload resolution, and so is still delayed until the class is complete.
Thanks a lot for the explanation!
(In reply to Jonathan Wakely from comment #10) > (In reply to Fedor Chelnokov from comment #7) > > This struct definition: > > ``` > > struct A { > > struct B { > > int i = 0; > > B() {} > > This declares default constructor, meaning that the type is default > constructible. Period. > > By declaring B(); you assert that B is default constructible. > > > }; > > A(B = {}); > > This is valid, because the user-provided default constructor for B is all > the compiler needs to see to know that B={} is valid. > > > }; > > ``` > > is accepted by GCC, but another one ({} replaced with = default) is not: > > ``` > > struct C { > > struct D { > > int i = 0; > > D() = default; > > This declares a default constructor that might be defined implicitly by the > compiler, **or** it might get deleted if the member definitions of D would > make the implicit default constructor ill-formed. This is obviously very > different from the case where you declare B(); There is no assertion that D > is default constructible, the compiler has to deduce whether or not that's > true. That depends on the default member initializer for D::i. The > initializer (which is just '0' here) is a "complete class context" which > means it is not processed until the class D is complete (this allows you to > use other members, or e.g. sizeof(D) as the initializer). > > A nested class like C::D is not complete until its enclosing class is > complete. This means the initializer for C::D::i is compiled after C is > complete. This means whether C::D is default constructible is not known > until C is complete. > > > > }; > > C(D = {}); > > This requires checking whether C::D is default constructible, but we are > still in the body of C, so C is not complete, which means C::D is not > complete, which means we don't know if C::D is default constructible. > > > }; > > ``` > > Demo: https://gcc.godbolt.org/z/WTPdTn1Yf > > > > Could you please explain why? I though that both must be same accepted or > > same rejected. > > I hope the explanation above helps. > > GCC trunk now has a tweak to parse simple initializers like 0 immediately, > instead of waiting for the class to be complete (because 0 doesn't depend on > anything in the class). But for the original example in comment 0 the > initializer std::numeric_limits<double>::max(); has to perform name lookup > and overload resolution, and so is still delayed until the class is complete. This is an excellent explanation, hope it benefits everyone who got here from Google search.
(In reply to Jonathan Wakely from comment #10) > (In reply to Fedor Chelnokov from comment #7) > > This struct definition: > > ``` > > struct A { > > struct B { > > int i = 0; > > B() {} > > This declares default constructor, meaning that the type is default > constructible. Period. > > By declaring B(); you assert that B is default constructible. > > > }; > > A(B = {}); > > This is valid, because the user-provided default constructor for B is all > the compiler needs to see to know that B={} is valid. > > > }; > > ``` > > is accepted by GCC, but another one ({} replaced with = default) is not: > > ``` > > struct C { > > struct D { > > int i = 0; > > D() = default; > > This declares a default constructor that might be defined implicitly by the > compiler, **or** it might get deleted if the member definitions of D would > make the implicit default constructor ill-formed. This is obviously very > different from the case where you declare B(); There is no assertion that D > is default constructible, the compiler has to deduce whether or not that's > true. That depends on the default member initializer for D::i. The > initializer (which is just '0' here) is a "complete class context" which > means it is not processed until the class D is complete (this allows you to > use other members, or e.g. sizeof(D) as the initializer). > > A nested class like C::D is not complete until its enclosing class is > complete. This means the initializer for C::D::i is compiled after C is > complete. This means whether C::D is default constructible is not known > until C is complete. > > > > }; > > C(D = {}); > > This requires checking whether C::D is default constructible, but we are > still in the body of C, so C is not complete, which means C::D is not > complete, which means we don't know if C::D is default constructible. Does it? I thought the default arguments were only evaluated at the point where they are actually used, i.e. in this case - when `C` constructor is invoked with no arguments. > > }; > > ``` > > Demo: https://gcc.godbolt.org/z/WTPdTn1Yf > > > > Could you please explain why? I though that both must be same accepted or > > same rejected. > > I hope the explanation above helps. Thank you for the explanation. If this is how the standard specifies compiler behavior, I wonder if this should be considered as a defect (DR). There is nothing in `std::numeric_limits<double>::max()` that depends on the definition of `C`, so there is no reason to prevent it from compiling.
(In reply to andysem from comment #13) > (In reply to Jonathan Wakely from comment #10) > > > C(D = {}); > > > > This requires checking whether C::D is default constructible, but we are > > still in the body of C, so C is not complete, which means C::D is not > > complete, which means we don't know if C::D is default constructible. > > Does it? I thought the default arguments were only evaluated at the point > where they are actually used, i.e. in this case - when `C` constructor is > invoked with no arguments. It's surprising, I agree. Whether the default argument is valid determines whether this constructor is considered a default constructor, which determines whether the compiler needs to implicitly declare a separate default constructor I think that's why the default argument is needed earlier than you expect. > If this is how the standard specifies compiler behavior, I wonder if this > should be considered as a defect (DR). I closed this as a dup of PR 96645 which has a DR number right there in the title. Like I said, see there for more details. > There is nothing in > `std::numeric_limits<double>::max()` that depends on the definition of `C`, > so there is no reason to prevent it from compiling. Are you sure? What if C::std is declared? Then the initializer means something very different. Again, see PR 96645 for further discussion of when the initializer can be compiled (and a possible patch to do it differently).