In general if you can construct a foo_t
in the constructor bodies of some class (without member initializer lists), then, you can modify your code so that your class now has a foo_t
attribute and its constructors either delegate construction or construct it inside their member initializer lists.
Basically, in most cases, you can rewrite your problematic constructor so that it delegates to another constructor while providing it with necessary information to construct a foo_t
instance in the member initializer list (which I quickly and informally illustrated in the comments with the following "example" https://ideone.com/ubbbb7 )
More generally, and if the tuple construction would happen to be a problem for some reason, the following transformation will (in general) work. It's admittedly a bit long (and ugly), but bear in mind it's for generality sake and that one could probably simplify things in practice.
Let's assume we have a constructor where we construct a foo_t
, for the sake of simplicity, we'll further assume it to be of the following form :
C::C(T1 arg_1, T2 arg_2) {
side_effects(arg_1, arg_2);
TL1 local(arg_1, arg_2);
second_side_effects(arg_1, arg_2, local);
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
Where the function calls possibly mutate the arguments.
We can delegate once to eliminate the declaration of local_1
in the constructor body, then once again to get rid of the call to second_side_effects(arg_1, arg_2, local)
.
C::C(T1 arg_1, T2 arg_2)
: C::C(arg_1, arg_2
,([](T1& a, T2& b){
side_effects(a, b);
}(arg_1, arg_2), TL1(a, b))) {}
C::C(T1& arg_1, T2& arg_2, TL1&& local)
: C::C(arg_1, arg_2
,[](T1& a, T2& b, TL1& c) -> TL1& {
second_side_effects(a, b, c);
return c;
}(arg_1, arg_2, local)) {}
C::C(T1& arg_1, T2& arg_2, TL1& local) {
foo_t f(arg_1, arg_2, local); // the actual construction
final_side_effects(arg_1, arg_2, local, f);
}
live example
Clearly, f
could be made an actual member of C and be constructed in the member initialization list of that last constructor.
One could generalize for any number of local variables (and arguments). I however assumed that our initial constructor didn't have any member initializer list. If it had one, we may have needed to either:
- copy some of the initial
arg_i
's before they were mutated and pass the copies along the constructor chain so that they could ultimately be used to construct the other members in the member initializer list
- preconstruct instances of the members and pass them along the constructor chain so that they could ultimately be used to move-construct the actual members in the member initializer list
The latter must be chosen if for some reason, the constructor of a member would have side effects.
There is however a case where this all falls apart. Let's consider the following scenario:
#include <memory>
struct state_t; // non copyable, non movable
// irreversible function that mutates an instance of state_t
state_t& next_state(state_t&);
struct foo_t {
foo_t() = delete;
foo_t(const foo_t&) = delete;
foo_t(const state_t&);
};
// definitions are elsewhere
class C {
public:
struct x_first_tag {};
struct y_first_tag {};
// this constructor prevents us from reordering x and y
C(state_t& s, x_first_tag = {})
: x(new foo_t(s))
, y(next_state(s)) {}
// if x and y were both constructed in the member initializer list
// x would be constructed before y
// but the construction of y requires the original s which will
// be definitively lost when we're ready to construct x !
C(state_t& s, y_first_tag = {})
: x(nullptr)
, y(s) {
next_state(s);
x.reset(new foo_t(s));
}
private:
std::unique_ptr<foo_t> x; // can we make that a foo_t ?
foo_t y;
};
In that situation, I admittedly have no idea how to rewrite this class, but I deem it rare enough to not really matter.