Tell me more ×
Stack Overflow is a question and answer site for professional and enthusiast programmers. It's 100% free, no registration required.

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?

share|improve this question
3  
My G++ 4.7.3 says internal compiler error: Segmentation fault to this code... – larsmans Jul 2 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 2 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 2 at 16:15
2  
Hmm, adding a constructor to B seems to make this work in gcc 4.7. – Shafik Yaghmour Jul 2 at 16:28
6  
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 2 at 16:29
show 4 more comments

2 Answers

up vote 41 down vote accepted

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 if 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()).

share|improve this answer
+1 nice answer! – Shafik Yaghmour Jul 2 at 21:28
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 2 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 3 at 4: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 3 at 6:27
@aschepler Yes, the diagnostic here is not very good. I've filed llvm.org/PR16550 for that. – Richard Smith Jul 5 at 17:34
show 2 more comments

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.

share|improve this answer
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 2 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 2 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 2 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 2 at 21:38

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.