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! :)
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.
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With