I start from a scenario like this:
Foo1
, Foo2
, and Baz
are untouchable
struct Foo1 { Foo1(int); Foo1(Foo1&&) = delete; }; // I can't touch it
struct Foo2 { Foo2(int); Foo2(Foo2&&) = delete; }; // I can't touch it
struct Baz { Baz(Foo1&); Baz(Foo2&); Baz(Baz&&) = delete; }; // I can't touch it
Bar
, I have control of, looks initially like this:
struct Bar {
Bar(int i)
: i{i}
, foo{i}
, baz{foo}
{}
int i;
Foo1 foo;
Baz baz;
};
At a later point, the need arises that Bar::foo
be actually either a Foo1
or a Foo2
depending on the i
fed to Bar
's constructor.
My initial thought was to use a std::variant
, but changing Bar
to fulfil the new requirement is not entirely straightforward, because the non-copiable-nor-movable-ity of Foo1
, Foo2
, and Baz
, gets in the way:
Foo1 foo;
to std::variant<Foo1, Foo2> foo;
causes in the initialization foo{i}
to fail to compile, and that's normal, but let's also simplify things by saying that for now we're still only putting a Foo1
in foo
, by changing also foo{i}
to foo{Foo1{i}}
; this still doesn't work because of the non-movability of Foo1
;Foo1
in place in the std::variant
; this would be viable, if Bar
did not have a Baz
member, because I could make the std::variant
default constructible by including std::monostate
in it (and this would also allow the conditional construction of Foo1
or Foo2
:
struct Bar {
Bar(int i)
: i{i}
//, baz{foo}
{
if (i == 0) {
foo.emplace<Foo1>(i);
} else {
foo.emplace<Foo2>(i);
}
}
int i;
std::variant<std::monostate, Foo1, Foo2> foo{};
//Baz baz;
};
The above observations lead me to think that a solution could be using std::unique_ptr
, but it does too require quite a bit of code, because one must std::visit
in order to construct Baz
:
struct Bar {
using Var = std::variant<std::unique_ptr<Foo1>, std::unique_ptr<Foo2>>;
Bar(int i)
: i{i}
, foo{i == 0 ? Var{std::make_unique<Foo1>(i)} : Var{std::make_unique<Foo2>(i)}}
, baz{std::visit([](auto&& p) { return Baz{*p}; }, foo)}
{}
int i;
Var foo;
Baz baz;
};
I was wondering if I have missed some cleaner way to deal with such a scenario.
You can construct the variant in place, no need of std::monostate
:
struct Bar {
using Foo = std::variant<Foo1, Foo2>;
Bar(int i)
: i{i}
, foo{i == 0 ? Foo(std::in_place_type<Foo1>, i) : Foo(std::in_place_type<Foo2>, i)}
, baz{std::visit([](auto&& p) { return Baz{p}; }, foo)}
{}
int i;
std::variant<Foo1, Foo2> foo;
Baz baz;
};
Unfortunately, msvc rejects that code, but using intermediate function (as lambda) works :
struct Bar {
using Foo = std::variant<Foo1, Foo2>;
Bar(int i)
: i{i}
, foo{[](int i){
if (i == 0) {
return Foo(std::in_place_type<Foo1>, i);
} else {
return Foo(std::in_place_type<Foo2>, i);
}
}(i)}
, baz{std::visit([](auto&& p) { return Baz{p}; }, foo)}
{}
int i;
std::variant<Foo1, Foo2> foo;
Baz baz;
};