c++smart-pointersmove-semanticsvariant

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.


Solution

  • 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.