3

I have 2 structs which hold different data and each one has a method to serialize this data into a JSON string:

struct Struct1
{
  Struct1(int value) : value(value){};

  int value;

  std::string ToJSON() const
  {
    std::string ret = "{value: " + std::to_string(value) + "}";
    return ret;
  }
};

struct Struct2
{
  Struct2(std::string id, std::string image, std::string name) : id(id), image(image), name(name){};

  std::string id;
  std::string image;
  std::string name;

  std::string ToJSON() const
  {
    return "{id: " + id + " image: " + image + " name: " + name + "}";
  }
};

I need to store several of them in one container in order to iterate over them later and get the JSON string from each object. I do this using a std::variant.

std::vector<std::variant<Struct1, Struct2>> v3;

I can then iterate over the container like this:

auto GetJSONString = [](auto&& _in){return _in.ToJSON();};

for (const auto& nextV : v3)
{
  auto test = std::visit(GetJSONString, nextV);
  std::cout << test << " ";
}
std::cout << std::endl;

Everything is working fine until I try to use braced-init-lists to fill the vector.

In other words, this is working:

std::vector<std::variant<Struct1, Struct2>> v{Struct1(5), Struct2("someid", "someimage", "somename")};

But this not:

std::vector<std::variant<Struct1, Struct2>> v4{ {13}, {"someid", "someimage", "somename"}};

On the not working code I get the following compiler error:

error: no matching function for call to 'std::vector<std::variant<Struct2, Struct1> >::vector(<brace-enclosed initializer list>)

I do not understand why is this. Can't I use brace initialization in this case? If so,... why? Or do I need to modify my structs in some way that they support this kind of initialization?

Here is a minimal working example on wandbox.org to further illustrate my problem.

Praetorian
  • 106,671
  • 19
  • 240
  • 328
p0fi
  • 1,056
  • 12
  • 28

2 Answers2

4

Can't I use brace initialization in this case? If so,... why? Or do I need to modify my structs in some way that they support this kind of initialization?

No, you cannot. And no, there is no change you can make to make it work. Here's a really simplified example:

template <typename T> void foo(T&& );
foo({1}); // what is T?

Braced-init-lists don't have a type, so they cannot be deduced. The way that variant's constructor works is by deducing its argument and then picking the best alternative to initialize from it. It can't do that from your braced-init-list, you have to use an expression that has a type.

The only way this could be made to work is if variant<A, B, C> itself had non-template constructors variant(A&& ), variant(B&& ), and variant(C&& ). Then, braced-init-lists would work.

Barry
  • 286,269
  • 29
  • 621
  • 977
2

The variant constructor you intend for your code to call is

template<typename T>
constexpr variant(T&& t)

But template argument deduction for that constructor template will fail because braced-init-lists do not have a type.

You can get a little bit closer by using the constructor that takes std::in_place_type_t<T> as the first argument

std::vector<std::variant<Struct2, Struct1>> v4{
     { std::in_place_type<Struct1>, 13 },
     { std::in_place_type<Struct2>, "someid", "someimage", "somename" }
};

But that fails too because the variant constructor in question is explicit, which is ill-formed for copy-list-initialization.

So the option you've listed is the best one

std::vector<std::variant<Struct2, Struct1>> v4{
     Struct1{ 13 },
     Struct2{ "someid", "someimage", "somename" }
};
Praetorian
  • 106,671
  • 19
  • 240
  • 328