92

The following code is quite trivial and I expected that it should compile fine.

struct A
{
    struct B
    {
        int i = 0;
    };

    B b;

    A(const B& _b = B())
        : b(_b)
    {}
};

I've tested this code with g++ version 4.7.2, 4.8.1, clang++ 3.2 and 3.3. Apart from fact that g++ 4.7.2 segfaults on this code (http://gcc.gnu.org/bugzilla/show_bug.cgi?id=57770), the other tested compilers give error messages that don't explain much.

g++ 4.8.1:

test.cpp: In constructor ‘constexpr A::B::B()’:
test.cpp:3:12: error: constructor required before non-static data member for ‘A::B::i’ has been parsed
     struct B
            ^
test.cpp: At global scope:
test.cpp:11:23: note: synthesized method ‘constexpr A::B::B()’ first required here 
     A(const B& _b = B())
                       ^

clang++ 3.2 and 3.3:

test.cpp:11:21: error: defaulted default constructor of 'B' cannot be used by non-static data member initializer which appears before end of class definition
    A(const B& _b = B())
                    ^

Making this code compilable is possible and seems like it should make no difference. There are two options:

struct B
{
    int i = 0;
    B(){} // using B()=default; works only for clang++
};

or

struct B
{
    int i;
    B() : i(0) {} // classic c++98 initialization
};

Is this code really incorrect or are the compilers wrong?

Jesse Good
  • 50,901
  • 14
  • 124
  • 166
etam1024
  • 843
  • 1
  • 7
  • 17
  • 3
    My G++ 4.7.3 says `internal compiler error: Segmentation fault` to this code... – Fred Foo Jul 02 '13 at 16:08
  • 2
    (error C2864: 'A::B::i' : only static const integral data members can be initialized within a class) is what VC2010 says. That output agrees with g++. Clang says it too, though it makes much less sense. You can't default a variable in a struct by doing `int i = 0` unless it is `static const int i = 0`. – Chris Cooper Jul 02 '13 at 16:09
  • @Borgleader: BTW I'd avoid the temptation to think of the expression `B()` as a function call to a constructor. You _never_ directly "call" a constructor. Think of this as special syntax that creates a temporary `B`... and the constructor is invoked as just one part of that process, deep within the mechanism that follows. – Lightness Races in Orbit Jul 02 '13 at 16:15
  • 2
    Hmm, adding a constructor to `B` seems to make this work in `gcc 4.7`. – Shafik Yaghmour Jul 02 '13 at 16:28
  • 7
    Interestingly, moving the definition of A's constructor out of A also seems to make it work (g++ 4.7); which chimes with "defaulted default constructor cannot be used... before end of class definition". – moonshadow Jul 02 '13 at 16:29
  • @ChrisCooper - member `int i=0` *is* allowed in C++11 but is currently not implemented in VS(2010,2012,2013), hence the error there. – Martin Ba Jul 02 '13 at 18:16
  • @moonshadow ah: it says "can I use defaulted default? Well, I have to wait until I'm out of the class. What class am I in? I am in both `A` and `B`. I'll wait until I'm out of both before I can safely use the defaulted default, that sounds good, right?" mayhap. – Yakk - Adam Nevraumont Jul 02 '13 at 18:46
  • @Yakk: but surely that can't be the whole problem - since we're actually already done declaring struct B at the point where B() is invoked, so we're not in fact "in both `A` and `B`"? The standard implies the compiler needs to know whether the default parameter of A() is a compile time constant expression, which it would be unable to deduce without B() being fully declared, which is the reason for the restriction, but AFAICT in the sample above it can in fact know that, so the error given is spurious. Unless there's some bit of wording I'm missing? – moonshadow Jul 02 '13 at 18:51
  • I think we've pretty much established that the immediate cause of the problem is trying to reference `B`'s default constructor before `A` is fully defined. What is left unclear is whether this pattern is something we actually expect to work - i.e. failure to compile is a bug common to many compilers - or whether this is something the standard explicitly leaves undefined / illegal. There is no one rule AFAICT explicitly mentioning this exact situation, but repeated rereadings of the relevant sections still leave me unable to say for certain this code is definitely legal. – moonshadow Jul 02 '13 at 19:32

2 Answers2

89

Is this code really incorrect or are the compilers wrong?

Well, neither. The standard has a defect -- it says both that A is considered complete while parsing the initializer for B::i, and that B::B() (which uses the initializer for B::i) can be used within the definition of A. That's clearly cyclic. Consider this:

struct A {
  struct B {
    int i = (A(), 0);
  };
  A() noexcept(!noexcept(B()));
};

This has a contradiction: B::B() is implicitly noexcept iff A() does not throw, and A() does not throw iff B::B() is not noexcept. There are a number of other cycles and contradictions in this area.

This is tracked by core issues 1360 and 1397. Note in particular this note in core issue 1397:

Perhaps the best way of addressing this would be to make it ill-formed for a non-static data member initializer to use a defaulted constructor of its class.

That's a special case of the rule that I implemented in Clang to resolve this issue. Clang's rule is that a defaulted default constructor for a class cannot be used before the non-static data member initializers for that class are parsed. Hence Clang issues a diagnostic here:

    A(const B& _b = B())
                    ^

... because Clang parses default arguments before it parses default initializers, and this default argument would require B's default initializers to have already been parsed (in order to implicitly define B::B()).

Columbo
  • 60,038
  • 8
  • 155
  • 203
Richard Smith
  • 13,696
  • 56
  • 78
  • Good to know. But the error message is still misleading, since the constructor is not in fact "used by non-static data member initializer". – aschepler Jul 02 '13 at 21:34
  • Did you know this because of a particular past experience with this issue, or just by carefully reading the standard (and list of defects)? Also, +1. – Cornstalks Jul 03 '13 at 04:53
  • +1 for this detailed answer. So what would be the way out? Some sort of "2-phase class parsing", where the parsing of outer class members that depend on inner classes is delayed until the inner classes have been fully formed? – TemplateRex Jul 03 '13 at 06:27
  • 4
    @aschepler Yes, the diagnostic here is not very good. I've filed llvm.org/PR16550 for that. – Richard Smith Jul 05 '13 at 17:34
  • @Cornstalks I discovered this issue while implementing initializers for non-static data members in Clang. – Richard Smith Jul 05 '13 at 17:34
  • @TemplateRex Lazy parsing of the delay-parsed components of class definitions would help a lot. The standard is sadly unclear on the order in which the delay-parsed class components are parsed -- see, for instance, [core issue 361](http://www.open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#361) -- and some compilers allow nested classes to use later lazy-parsed components of surrounding classes, so an inside-out ordering risks breaking existing code. – Richard Smith Jul 05 '13 at 17:42
  • `There are a number of other cycles and contradictions in this area.` Do you maybe have another example that will compile/crash in Visual Studio 2013? I'm interested, because VS2013 accepts OP's code just fine. – typ1232 May 01 '14 at 11:34
0

Maybe this is the problem:

§12.1 5. A default constructor that is defaulted and not defined as deleted is implicitly defined when it is odr- used (3.2) to create an object of its class type (1.8) or when it is explicitly defaulted after its first declaration

So, the default constructor is generated when first looked up, but the lookup will fail because A is not completely defined and B inside A will therefore not be found.

fscan
  • 433
  • 2
  • 9
  • I'm not sure about that "therefore". Clearly `B b` isn't a problem, and finding explicit methods/ an explicitly declared constructor in `B` is not a problem. So it would be nice to see some definition of why the lookup should proceed differently here so that "`B` inside `A` is not found" in just this one case but not the others, before we can declare the code illegal for this reason. – moonshadow Jul 02 '13 at 19:35
  • I found words in the standard that the class definition is considered complete during in-class initialization, including within nested classes. I didn't bother to record the reference as it didn't seem relevant. – Mark B Jul 02 '13 at 19:36
  • @moonshadow: the statement says implicitly defaulted constructors are defined when odr- used. explicitly is defined after first declaration. And B b does not call an constructor, the constructor of A calls constructor of B – fscan Jul 02 '13 at 19:40
  • If anything like this were the problem, the code would still be invalid if you remove the `=0` from `i = 0;`. But without that `=0`, the code is valid and you won't find a single compiler that complains about using `B()` within the definition of `A`. – aschepler Jul 02 '13 at 21:38