The Vee
The Vee

Reputation: 11570

Can I avoid explicitly writing a constructor for each struct in a std::variant?

Consider this code:

#include <variant>

struct x {
  int y;
};

int main() {
  std::variant<x> v(std::in_place_type<x>, {3}); /*1*/
  return std::get<x>(v).y;
}

This does not compile and neither does when removing the {} from the line /*1*/, even though aggregate initialization

x a{3};
x b({3});

works in both "constructor-like" forms. Can I somehow make the std::variant initializer aware of the possibility of constructing structs using aggregate initialization without having to write boring boilerplate constructors for each struct that may be used in my real-world case?

I would expect this to work, somehow, as per cppreference the two overloads (5) and (6) in question both say

Constructs a variant with the specified alternative T and initializes the contained value with the arguments [...]

I'm using GCC 7 if that matters.

Upvotes: 9

Views: 999

Answers (3)

Barry
Barry

Reputation: 303537

If you want to go overkill, we can create a factory type that has a conversion operator:

template <class... Args>
struct list_init_from {
    std::tuple<Args...> args;

    template <class T>
    operator T() {
        return std::apply([](auto... args){
            return T{args...};
        }, args);
    }   
};

template <class... Args>
list_init_from(Args... ) -> list_init_from<Args...>;

Which you can use:

std::variant<x> v(std::in_place_type<x>, list_init_from{3});

This works, but leaves much to be desired: perfect forwarding, SFINAE on the conversion operator, and explicitly specifying which types to allow conversions to are exercises left to the reader.

Upvotes: 0

Rakete1111
Rakete1111

Reputation: 49018

There is no workaround for this, apart from adding a constructor. The standard mandates this for both overloads you mention, [variant.ctor]19 and [variant.ctor]23 respectively:

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments std​::​forward<Args>(args)....

Effects: Initializes the contained value as if direct-non-list-initializing an object of type T with the arguments il, std​::​forward<Args>(args)....

You can always copy or move the object using:

std::variant<x> v(std::in_place_type<x>, x{3});
// or more clear and does the same thing
std::variant<x> v(x{3});

Upvotes: 2

cbuchart
cbuchart

Reputation: 11575

Maybe it is not exactly what you are asking, but what about explicitly constructing the object instead of relying on type inference?

#include <variant>

struct x {
  int y;
};

int main() {
  std::variant<x> v(std::in_place_type<x>, x{3});
  return std::get<x>(v).y;
}

Upvotes: 3

Related Questions