Search code examples
c++constructorinitializationlanguage-lawyermember-initialization

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


Consider this class:

#include <iostream>

struct foo {
    int a = 42;
    int b = bar();
    int bar() { return a; }
};

int main(){
    foo f;
    std::cout << f.a << " " << f.b;
}

It prints the expected 42 42. Is it allowed by the standard to call a member function in a default member initializer?

The following I would expect to be undefined:

struct broken {
    int a = bar();
    int b = 42;       
    int bar() { return b; }
};

Unfortunately it does compile without warnings.


Solution

  • 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.