Question

pushing a unique_ptr into a vector of variant unique_ptr

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.

 2  104  2
1 Jan 1970

Solution

 6

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.

2024-07-21
Fran&#231;ois Andrieux

Solution

 1

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>());
}
2024-07-21
Remy Lebeau