Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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

Tags:

c++

c++11

I'm an old C-dude that tries to learn about C++11 by porting my old state-machine framework from C to C++11. My idea is to have a class for the state-machine itself and then nested classes for the states within. The states may be hierarchical, i.e. super- and substates. The framework needs to know about a state's superstate and for that I have a pointer (state *superstate) in the nested state class.

My problem is that I intended to set the superstate-pointer by using the constructor directly within the machine's class, which should be possible in C++11 with non-static data member initialization, by using uniform initialization. But some reason it fails to compile (substateB3{superstateA}) when set to another type of state/class. But it works fine if I later set it by using a specific function (set_superstate) for this purpose, which has the same argument as the constructor! And funny enough the constructor is accepted if I set the superstate to a state/class of same type (substateB2{substateB1}).

I'm using gcc 4.7.0 (to get support for non-static data member initializers) and here's my code:

// My state-machine framework (simplified)
struct machine {
  struct state {
    state()                  : superstate(nullptr) { }     // No superstate => toplevel state!
    state(state &superstate) : superstate(&superstate) { }
    state *superstate;
    void set_superstate(state &superstate) { this->superstate = &superstate; } // Non-ctor way to set superstate
  };
};

// An example of a specific state-machine using my framework
struct Machine : machine {
  struct SuperstateA : state {
  } superstateA;

  struct SubstateB : state {
  } substateB1,              // Compiles OK; gets its superstate set in Machine's ctor below
    substateB2{substateB1},  // Compiles OK; but not correct superstate
    substateB3{superstateA}; // gcc 4.7.0 error: could not convert ‘{((Machine*)this)->Machine::superstateA}’ from ‘<brace-enclosed initializer list>’ to ‘Machine::SubstateB’

  Machine()  { substateB1.set_superstate(superstateA); } // Compiles OK;
} myMachine;

Any tips or guidance are much appreciated thanks! :)

like image 895
Jens Schwarzer Avatar asked May 20 '12 21:05

Jens Schwarzer


2 Answers

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.

like image 61
jthill Avatar answered Nov 15 '22 13:11

jthill


  struct SuperstateA : state {
  } superstateA;

  struct SubstateB : state {
  } substateB1, 
    substateB3{superstateA};

There is no relationship between SuperstateA and SubstateB that would allow casting or slicing between them. Even if SuperstateA were a base class (which is usually associated with "super"), SubstateB would still have more members (being a subclass) and it would be impossible to initialize them all from the object of different type.

It's not a bug in the compiler, or an arbitrary limitation of C++. It's simply impossible to perform that kind of initialization. There needs to be an "is-a" relationship.

On the other hand, both superstateA and substateB1 can be cast to state & (or their pointers can be cast to state *), so providing SubstateB::SubstateB( state & ) would patch all the holes quite nicely.

like image 37
Potatoswatter Avatar answered Nov 15 '22 12:11

Potatoswatter