元组、向量和初始化列表

33

我尝试使用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'}};

然而,对于元组向量,编译器会报错:

错误:从初始化列表转换为“std::tuple”将使用显式构造函数“constexpr std::tuple< > ::tuple(_UElements&& …)[with _UElements = {int,double,char}; = void; _Elements = {int,double,char}]”

编译器提供的错误信息对我来说完全无用,我不知道 tuple 的构造函数是如何实现的,但我知道它们支持统一初始化(比如:tuple<int,float,char>{1,2.2,'X'}),因此,我想知道我遇到的问题是编译器的待办事项还是 C++11 标准规定的。

4个回答

15

std::tuple 相关的构造函数是 explicit 的。这意味着你想做的事情是不可能的,因为你想要使用的语法是基于复制初始化的(这禁止了调用 explicit 构造函数)。相反,std::tuple<int, float, char> { 1, 2.2, 'X' } 使用直接初始化。只有 std::pair 拥有非 explicit 构造函数。

要么使用直接初始化,要么使用标准元组工厂函数之一(例如 std::make_tuple)。


1
在C++17中,它已被有条件地显式声明为非显式。 - L. F.

2

这实际上是可行的,使用c++11特性。

是的,initializer_list希望它的所有元素都是相同类型的。技巧在于我们可以创建一个包装类,可以static_cast成我们想要的所有类型。这很容易实现:

 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_;
 };

使用隐式转换构造函数,我们可以将类MultiTypeWrapper的初始化列表(或隐式转换为初始化列表的向量)传递给像{1,2.5,'c',4}这样的内容。这意味着我们不能编写一个接受此类初始化列表作为参数的函数,如下所示:
template <typename... T>
std::tuple<T...> create_tuple(std::vector<unit_test::MultiTypeWrapper<T...> > init) {
  ....
}

我们使用另一种技巧将向量中的每个值转换为其原始类型(请注意,在MultiTypeWrapper的定义中,我们提供了隐式转换),并将其分配给元组中的相应插槽。这就像模板参数上的递归:

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;
}

请注意,我们需要创建set_tuple的辅助类,因为c++不支持函数特化。现在,如果我们想测试代码:
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());

输出结果将会是:
1 2.50 ABC

这是在我的桌面上使用clang 3.2进行测试的。

希望我的输入能有所帮助 :)


1
很酷,但我不认为OP想要编写一个包装类只是为了能够将元组推入向量中。至少,我不会这样做。 - Avrdan

0

你不能仅使用大括号来初始化元组,必须使用关键字tuple。

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

C++ 11


0

很烦人,不是吗?我之前也在类似的情况下使用过pairs,并惊讶地发现元组不支持此功能,因为{}初始化语法可以节省很多代码。您可以使用make_tuple手动将元素插入容器中,例如:

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

应该可以工作


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接