Reputation: 31
I am playing around with C++ and faced this problem.
If I have a variant
that looks like this:
using Fruit = variant<Apple, Tomato>
and a vector
of unique_ptr
of Fruit
:
vector<unique_ptr<Fruit>> fruits
How can I move a unique_ptr<Apple>
into the fruits
vector?
Here is an example piece of code:
#include <variant>
#include <memory>
using namespace std;
struct Apple {};
struct Tomato {};
using Fruit = variant<Apple, Tomato>;
int main() {
vector<unique_ptr<Fruit>> fruits;
unique_ptr<Apple> apple = make_unique<Apple>();
fruits.push_back(unique_ptr<Fruit>(move(apple))); // error here
}
On the call to push_back
, I get this error:
No matching conversion for functional-style cast from 'remove_reference_t<unique_ptr<Apple, default_delete<Apple>> &>' (aka 'std::unique_ptr<Apple>') to 'unique_ptr<Fruit>' (aka 'unique_ptr<variant<Apple, Tomato>>')
If I change the line to:
fruits.push_back(unique_ptr<Apple>(move(apple)));
I get this error:
No matching member function for call to 'push_back'
And if I change it to this:
fruits.emplace_back(unique_ptr<Apple>(move(apple)));
no error occurs.
So, is using emplace_back()
the right choice here?
Why does this error occur? I am assuming it is because I can't cast a unique_ptr<VariantMemberType>
to a unique_ptr<VariantType>
?
EDIT:
emplace_back()
results in a compile-time error, LSP didn't give errors so I assumed it was valid and forgot to actually compile it.
Upvotes: 2
Views: 154
Reputation: 597941
You are storing pointers to Fruit
, so you need to construct Fruit
objects. And since Fruit
is a variant
, you have to assign the desired type of value to each Fruit
object, eg:
#include <vector>
#include <variant>
#include <memory>
using namespace std;
struct Apple {};
struct Tomato {};
using Fruit = variant<Apple, Tomato>;
int main() {
vector<unique_ptr<Fruit>> fruits;
auto apple = make_unique<Fruit>();
*apple = Apple{};
// or: auto apple = make_unique<Fruit>(Apple{});
// or: auto apple = make_unique<Fruit>(in_place_type<Apple>);
fruits.push_back(move(apple));
auto tomato = make_unique<Fruit>();
*tomato = Tomato{};
// or: auto tomato = make_unique<Fruit>(Tomato{});
// or: auto tomato = make_unique<Fruit>(in_place_type<Tomato>);
fruits.push_back(move(tomato));
}
That being said, your use of std::variant
makes no sense to me. A fruit should not be a choice between an apple or a tomato. Apple and tomato are types of fruits, so you should be using inheritance instead of variance.
Define Fruit
as a base class which Apple
and Tomato
derive from, then you can store Apple
and Tomato
pointers in your vector
of Fruit
pointers, eg:
#include <vector>
#include <memory>
using namespace std;
struct Fruit {
virtual ~Fruit() = default;
};
struct Apple : Fruit {};
struct Tomato : Fruit {};
int main() {
vector<unique_ptr<Fruit>> fruits;
auto apple = make_unique<Apple>();
fruits.push_back(move(apple));
// or: fruits.push_back(make_unique<Apple>());
auto tomato = make_unique<Tomato>();
fruits.push_back(move(tomato));
// or: fruits.push_back(make_unique<Tomato>());
}
Upvotes: 1
Reputation: 29032
Fruit
is unrelated to Apple
. A Fruit
just potentially contains an Apple
. There is no way to convert a pointer to Apple
to a pointer to Fruit
for the same reason you can't convert a pointer to int
to a pointer to std::set<int>
. The best you can do is make a new Fruit
object and move the value pointed to by apple
into the new Fruit
.
#include <memory>
#include <variant>
#include <vector>
struct Apple {};
struct Tomato {};
using Fruit = std::variant<Apple, Tomato>;
int main() {
std::vector<std::unique_ptr<Fruit>> fruits;
std::unique_ptr<Apple> apple = std::make_unique<Apple>();
fruits.push_back(std::make_unique<Fruit>(std::move(*apple)));
}
Notice that we now make a new Fruit
with std::make_unique
and move the value of the object pointed to by apple
instead of moving the pointer. It's worth noting that you will now have a moved-from Apple
pointed to by apple
. If you need its lifetime to end you need to reset the pointer manually.
Upvotes: 6