HTNW
HTNW

Reputation: 29193

How to guarantee copy elision with std::variant?

I have this type:

struct immobile {
   // other stuff omitted
   immobile(immobile&) = delete;
   immobile(immobile&&) = delete;
};
immobile mk_immobile();
// e.g. this compiles
// mk_immobile() is a prvalue and i is its result object
immobile i(mk_immobile());

I also have this class template:

template<typename T>
struct container {
    std::variant<T, other_stuff> var;
    template<typename... Args>
    container(Args&&... args)
    : var(std::in_place_index<0>, std::forward<Args>(args)...) {}
};

I want to construct a container around the object produced by mk_immobile(), with the immobile object used to initialize one of the variants of var.

container<immobile> c(mk_immobile());

However, this does not work. For one, std::variant's constructor wants std::is_constructible_v<immobile, immobile>, which doesn't hold. Worse, even this simplified version fails:

template<typename T>
struct demonstration {
    T t;
    template<typename... Args>
    demonstration(Args&&... args) : t(std::forward<Args>(args)...) {}
};
demonstration<immobile> d(mk_immobile());

Which seems to imply that std::forward does not, in fact, perfectly forward—prvalues do not forward as prvalues. (This makes sense to me; I don't think doing that would be possible.) I can make demonstration work by changing it to this:

template<typename T>
struct demonstration {
    T t;
    template<typename F>
    demonstration(F&& f) : t(std::forward<F>(f)()) {}
};
demonstration<immobile> d([] { return mk_immobile(); });

But I do not see a way to change container in a similar manner. How do I change container so that it can construct a std::variant (or other tagged union) out of a prvalue? I can change container but cannot change immobile.

Upvotes: 7

Views: 370

Answers (1)

Passer By
Passer By

Reputation: 21130

You abuse casts

template<typename F>
struct initializer
{
    F f;
    template<typename T>
    operator T()
    {
        return f();
    }
};

template<typename F>
initializer(F&&) -> initializer<F>;

And use as

container<immobile> c{initializer{[]{
    return mk_immobile();
}}};

Upvotes: 6

Related Questions