There are a couple of options that let you do this while retaining automatic storage, and which one you should use depends on the semantics of the type Object
.
The POD
If you have a type like the one given in the question, you might choose to reduce it to a POD type; basically, remove all of the user-provided constructors and give everything the same access specifier:
struct Object {
int mInt1, mInt2;
};
Then, your initialization pattern might look like this (using placement new):
Object o; // Default-initialized, a no-op
if (condition)
new (&o) Object {i};
else
new (&o) Object {i, j};
General Types
Generally speaking, your typical value-semantic type will work perfectly fine if you default-initialize and then assign, thanks to move semantics:
std::vector <foo> v;
if (condition)
v = std::vector <foo> (42);
else
v = std::vector <foo> {bar, baz, quux};
Often, though, you'll still be doing work in the default constructor, because certain types' default-constructed objects (e.g., std::vector
) have well-defined state. If you want to avoid this work for an arbitrary predefined type, you might want to use std::optional
(as of this writing not actually yet standard):
std::optional <big_but_flat> b;
if (condition)
b.emplace (i);
else
b.emplace (i, j);
Without std::optional
You might object that std::optional
has too much overhead associated with it, and I'll leave it to you and your measurements to decide whether that's the case. At any rate, we can get our behaviour without worrying about that overhead— but may the nasal demons have mercy if you don't actually perform your initialization. We'll use a union
to get what we want:
// At function scope
union store_v {
std::vector <int> v;
store_v () {}
~store_v () { v.~vector <int> (); }
} sv;
if (condition)
new (&sv.v) std::vector <int> (42);
else
new (&sv.v) std::vector <int> {49, 343, 2401};
This may be improved. For example, we can make the storage a template:
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
store <std::vector <int>> sv;
if (condition)
new (&sv.t) std::vector <int> (42);
else
new (&sv.t) std::vector <int> {49, 343, 2401};
We can give ourselves a reference:
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
store <std::vector <int>> sv;
auto&& v = sv.t; // Deduce const, for what that's worth
if (condition)
new (&v) std::vector <int> (42);
else
new (&v) std::vector <int> {49, 343, 2401};
And with a little attention to detail to avoid name collisions and handle C++'s… interesting declaration syntax, we could even define a couple of macros to clean the code up (implementation left as an exercise to the reader):
template <typename T>
union store {
T t;
store () {}
~store () { t.~T (); }
};
// At function scope
DECL_UNINIT (std::vector <int>, v);
if (condition)
INIT (v, (42));
else
INIT (v, {49, 343, 2401});