Error When Using In-Class Initialization of Non-Static Data Member and Nested Class Constructor

Error when using in-class initialization of non-static data member and nested class constructor

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

`constructor required before non-static data member` - Am I hitting c++ core issue 1360 with a `boost::variant`?

This doesn’t depend on anything being (found to be) constexpr, so it’s not identical to CWG1360, but it’s certainly similar in that it capitalizes on the impossibility of simultaneously having a nested class being a complete-class context for its containing class and having the nested class be complete later in the containing class than its definition. Perhaps the best locus is CWG2335, for which the general (long-term) proposal has been made that actual dependencies within a class should be tracked by an instantiation-like mechanism; examples like yours ask for such a treatment across classes.

C++ [Error] invalid use of non-static data member 'Tree::root'

Node *T=root is illegal in a default argument because default arguments do not have access to this. You can use two overloads instead, one with a Node* parameter and one without.

void Road(char p) { Road(p, root); }
void Road(char p, Node* T);

invalid use of non-static data member

In C++, unlike (say) Java, an instance of a nested class doesn't intrinsically belong to any instance of the enclosing class. So bar::getA doesn't have any specific instance of foo whose a it can be returning. I'm guessing that what you want is something like:

    class bar {
private:
foo * const owner;
public:
bar(foo & owner) : owner(&owner) { }
int getA() {return owner->a;}
};

But even for this you may have to make some changes, because in versions of C++ before C++11, unlike (again, say) Java, a nested class has no special access to its enclosing class, so it can't see the protected member a. This will depend on your compiler version. (Hat-tip to Ken Wayne VanderLinde for pointing out that C++11 has changed this.)

C++11 non-static data member uniform initialization fails for pointers to other classes of same base class

Stripping one layer of inheritance:

struct m {
struct state { state *s;
state() : s(0) {};
state(state &s) : s(&s) {}
set(state &s) { this->s = &s; }
};

struct s1 : state {} A; // calls s1(), the default constructor
struct s2 : state {} B // calls s2(), ditto
, C{B} // calls s2(const s2&), the default copy constructor
, D{A}; // calls s2(const s1&)

m() { B.set(A); } // The m() body runs after all the members are constructed.
} M;

You're getting the construction error because your substates declare no constructors so they're getting the compiler-provided defaults, and there is none from a sibling- or base- class reference (the compiler doesn't provide s2(s1&) or s2(state&)).

You're getting the wrong superstate for C because C{B} invokes the default copy constructor s2(s2&), which runs before m()'s body.

Here's what you want instead:

struct m {
struct state { state *s; state() : s(0) {} state(state &s) : s(&s) {} };
struct s1 : state {} A; // default-constructs, fine
struct s2 : state {
s2(state &s) : state(s) {}
s2(s2&s) : state(s) {}
} B // default-constructs
, C{B} // calls s2(s2&), invokes state(state&)
, D{A}; // calls s2(state&)
;
m() : B(A) {};
} M;

When M's constructor runs, first its base classes (there are none) then its members are constructed in declaration order using the specified initializations. There's only one: B(A), so all the rest are defaulted. After all the bases and members are constructed, then the object constructor's body runs.

Error: Invalid use of non-static data member, variable was not declared in this scope

Problem 1

You can't use what the compiler hasn't seen yet, so moving the declaration above the usage solves this problem. You've done this already. Good job.

Problem 2

height will not be known until the object is constructed. You can't construct the object if the size of the object is not known, and height determines the size of next which contributes to the size of the object. Catch 22.

Recommend using a std::vector in place of the array and initializing the vector with height in the constructors's Member Initializer Lists. Example:

std::vector<SkipList<Key_t, Mapped_t>::Node *> next;

Node(int height): height(height), next(height)
{
}
Node(Key_t key, Mapped_t obj, int height):
value(std::make_pair(key, obj)),
height(height),
next(height)
{
}

Is it allowed to call a non-static member function in a default member initializer?

As you found, this is legal, but brittle and not recommended. When you specify default initializers for class members those are just syntactic sugar for use this value in the class member initializer list. So, if we look at when we can call a member function we find [class.cdtor]/1 and [class.cdtor]/4 which states:

1) For an object with a non-trivial constructor, referring to any non-static member or base class of the object before the constructor begins execution results in undefined behavior. For an object with a non-trivial destructor, referring to any non-static member or base class of the object after the destructor finishes execution results in undefined behavior.

4) Member functions, including virtual functions ([class.virtual]), can be called during construction or destruction ([class.base.init]).[...]

emphasis mine

Since the constructor has begun executing, and we are allowed to call member functions, we are not in UB land.

The next thing we have to consider is construction order, since the members depend on that. That information is in [class.base.init]/13

Then, non-static data members are initialized in the order they were declared in the class definition (again regardless of the order of the mem-initializers).

So, the members are constructed in the order they are declared in the class which means in your first example you refer to a after it has been initialized so you are not in UB land.

In your second example you are referring to an object that has not yet been initialized and reading an uninitialized object's value is undefined behavior.



Related Topics



Leave a reply



Submit