Я начинаю с такого сценария:
Foo1, Foo2 и Baz неприкосновенны
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, которым я управляю, изначально выглядит так:
struct Bar {
Bar(int i)
: i{i}
, foo{i}
, baz{foo}
{}
int i;
Foo1 foo;
Baz baz;
};
Позже возникает необходимость, чтобы Bar::foo на самом деле был либо Foo1, либо Foo2 в зависимости от того, i передается конструктору Bar.
Моя первоначальная мысль заключалась в том, чтобы использовать std::variant, но изменить Bar для выполнения нового требования не совсем просто, потому что мешает некопируемая, Foo1 и Foo2 непереносимость:
Baz на Foo1 foo; приводит к тому, что при инициализации std::variant<Foo1, Foo2> foo; не удается скомпилировать, и это нормально, но давайте также упростим ситуацию, сказав, что на данный момент мы все еще добавляем только foo{i} в Foo1, изменив также foo на foo{i}; это все равно не работает из-за неподвижности foo{Foo1{i}};Foo1 на месте Foo1; это было бы жизнеспособно, если бы std::variant не имело члена Bar, потому что я мог бы создать конструкцию Baz по умолчанию, включив в нее std::variant (и это также позволило бы условную конструкцию std::monostate или Foo1:
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;
};
Вышеприведенные наблюдения наводят меня на мысль, что решение могло бы использовать Foo2, но для этого тоже потребуется довольно много кода, потому что необходимо std::unique_ptr, чтобы построить std::visit:
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;
};
Мне было интересно, не пропустил ли я какой-нибудь более чистый способ справиться с таким сценарием.
@PepijnKramer, я никак не могу редактировать Foo1 и Foo2, даже не добавляя к ним базовый класс. В любом случае, у них есть что-то общее, но я не знаком с этим кодом, поэтому нелегко узнать, насколько; с моей точки зрения, их объединяет то, что их обоих можно использовать для создания Baz.
Что-то должно позже использовать Foo1 или Foo2, и мне любопытно, что это за использование. Являются ли они просто держателями данных или чем-то, на основании чего принимаются меры?
@PepijnKramer, дело в том, что ctor База берет Foo1 или Foo2 по ссылке, использует их и ожидает, что они останутся в живых, поэтому мне тоже приходится поддерживать их жизнь в Var. Там (я имею в виду Foo1 и Foo2) безумный код, с которым я не знаком.





Вы можете построить вариант на месте, без необходимости 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;
};
К сожалению, msvc отклоняет этот код, но использование промежуточной функции (как лямбда) работает:
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;
};
Есть ли у Foo1 и Foo2 что-то общее, что нужно сделать Bar? Например. представляет ли функциональность абстрактный базовый класс? Если да, то у меня есть идея