tuple vector and initializer_list
Asked Answered
S

4

34

I tried to compile the following snippets with gcc4.7

vector<pair<int,char> > vp = {{1,'a'},{2,'b'}};
//For pair vector, it works like a charm.

vector<tuple<int,double,char> > vt = {{1,0.1,'a'},{2,4.2,'b'}};

However, for the vector of tuples, the compiler complains:

error: converting to ‘std::tuple’ from initializer list would use explicit constructor ‘constexpr std::tuple< >::tuple(_UElements&& ...) [with _UElements = {int, double, char}; = void; _Elements = {int, double, char}]’

The error info spilled by the compiler is total gibberish for me, and I have no idea how were the constructors of tuple implemented, yet I do know they're totally okay with uniform initialization (like: tuple<int,float,char>{1,2.2,'X'}), therefore, I wonder if the problem I encountered is only a TODO of the compiler or it's something defined by the C++11 standard.

Siler answered 15/9, 2012 at 10:28 Comment(0)
P
16

The relevant std::tuple constructors are explicit. This means that what you want to do is not possible, since the syntax you want to use is defined in terms of copy initialization (which forbids calling an explicit constructor). In contrast, std::tuple<int, float, char> { 1, 2.2, 'X' } uses direct initialization. std::pair does have non-explicit constructors only.

Either use direct-initialization or one of the Standard tuple factory function (e.g. std::make_tuple).

Prewitt answered 15/9, 2012 at 11:4 Comment(1)
It's been made conditionally explicit (non-explicit in this case) in C++17.Dynode
A
2

This is actually doable, with c++11 features.

Yes the initializer_list wants all its element to be of the same type. The trick is that we can create a wrapper class that can be static_cast to all the types we want. This is easy to achieve:

 template <typename... tlist>
 class MultiTypeWrapper {
 };

 template <typename H>
 class MultiTypeWrapper<H> {
 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

 template <typename H, typename... T>
 class MultiTypeWrapper<H, T...> 
   : public MultiTypeWrapper<T...> {

 public:
   MultiTypeWrapper() {}

   MultiTypeWrapper(const H &value) : value_(value) {}

   // If the current constructor does not match the type, pass to its ancestor.
   template <typename C>
   MultiTypeWrapper(const C &value) : MultiTypeWrapper<T...>(value) {}

   operator H () const {
     return value_;
   }
 private:
   H value_;
 };

With the implicit conversion constructors, we can pass something like {1,2.5,'c',4} to an initializer_list (or vector, which implicitly converts the initializer_list) of type MultiTypeWrapper. This means that we can not write a function like below to accept such intializer_list as argument:

template <typename... T>
std::tuple<T...> create_tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  ....
}

We use another trick to cast each value in the vector to its original type (note that we provide implicit conversion in the definition of MultiTypeWrapper) and assign it to the corresponding slot in a tuple. It's like a recursion on template arguments:

template <int ind, typename... T>
class helper {
public:
  static void set_tuple(std::tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<ind>(t) = static_cast<typename std::tuple_element<ind,std::tuple<T...> >::type>(v[ind]);
    helper<(ind-1),T...>::set_tuple(t,v);
  }
};



template <typename... T>
class helper<0, T...> {
public:
  static void set_tuple(std::tuple<T...> &t, const std::vector<MultiTypeWrapper<T...> >& v) {
    std::get<0>(t) = static_cast<typename std::tuple_element<0,std::tuple<T...> >::type>(v[0]);
  }
};



template <typename... T>
std::tuple<T...> create_tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  std::tuple<T...> res;
  helper<sizeof...(T)-1, T...>::set_tuple(res, init);
  return res;
}

Note that we have to create the helper class for set_tuple since c++ does not support function specialization. Now if we want to test the code:

auto t = create_tuple<int,double,std::string>({1,2.5,std::string("ABC")});
printf("%d %.2lf %s\n", std::get<0>(t), std::get<1>(t), std::get<2>(t).c_str());

The output would be:

1 2.50 ABC

This is tested on my desktop with clang 3.2

Hope my input helps :)

Allium answered 28/8, 2013 at 5:31 Comment(1)
Cool, but I don't think the OP was looking to write a wrapper class just to be able to push tuples into a vector. At least, I am not going to do that.Sat
S
1

Annoying isn't it? I was also using pairs before - in a similar scenario and was surprised tuples do not support this, as the {} initialization syntax saves a lot of clutter. You can insert the elements manually in containers using make_tuple, so:

vt.push_back(make_tuple(2,4.2,'b'));

should work

Sat answered 4/6, 2020 at 12:39 Comment(0)
K
0

you can't use the accolades just for initializing the tuple, you must use the keyword tuple instead

vector<tuple<int, int>> my_vec{
    tuple<int, int> { 1, 15 },
    tuple<int, int> { 2, 100 }
};

C++ 11

Kellda answered 1/9, 2021 at 22:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.