模板并不总是猜测初始化列表类型

时间:2011-10-08 21:43:22

标签: c++ templates c++11 initializer-list

#include <initializer_list>
#include <utility>

void foo(std::initializer_list<std::pair<int,int>>) {}
template <class T> void bar(T) {}

int main() {
    foo({{0,1}});  //This works
    foo({{0,1},{1,2}});  //This works
    bar({{0,1}});  //This warns
    bar({{0,1},{1,2}});  //This fails
    bar(std::initializer_list<std::pair<int,int>>({{0,1},{1,2}}));  //This works
}

这不能在gcc 4.5.3中编译,它会对标记的行deducing ‘T’ as ‘std::initializer_list<std::initializer_list<int> >’发出警告,并为标记的行no matching function for call to ‘bar(<brace-enclosed initializer list>)’发出错误。为什么gcc可以推断出第一次调用bar的类型而不是第二次调用的类型,除了漫长而丑陋的演员之外,有没有办法解决这个问题呢?

2 个答案:

答案 0 :(得分:19)

根据C ++ 11 的GCC无法推断前两次调用bar 的类型。它发出警告,因为它实现了对C ++ 11的扩展

标准说当函数模板调用中的函数参数是{ ... }并且参数不是initializer_list<X>(可选参考参数)时,那么参数的类型不能推导出来由{...}。如果参数 initializer_list<X>,那么初始化列表的元素将通过与X进行比较而独立推导出来,并且每个元素的扣除必须匹配。

template<typename T>
void f(initializer_list<T>);

int main() {
  f({1, 2}); // OK
  f({1, {2}}); // OK
  f({{1}, {2}}); // NOT OK
  f({1, 2.0}); // NOT OK
}

在这个例子中,第一个是OK,第二个也是OK,因为第一个元素产生类型int,第二个元素将{2}T进行比较 - 这个推论不能产生一个常量,因为它没有推导出任何东西,因此最终第二次调用将T作为int。第三个不能用任何元素推导T,因此不行。最后一次调用产生了两个元素的矛盾扣除。

使这项工作的一种方法是使用这种类型作为参数类型

template <class T> void bar(std::initializer_list<std::initializer_list<T>> x) {
  // ...
}

我应该注意,执行std::initializer_list<U>({...})是危险的 - 最好删除大括号周围的(...)。在你的情况下,它偶然发生,但考虑

std::initializer_list<int> v({1, 2, 3});
// oops, now 'v' contains dangling pointers - the backing data array is dead!

原因是({1, 2, 3})调用initializer_list<int>的复制/移动构造函数,将initializer_list<int>{1, 2, 3}相关联的临时v传递给v。然后,当初始化完成时,该临时对象将被销毁并死亡。当与列表关联的临时对象死亡时,保存数据的备份数组也将被销毁(如果移动被省略,它将与“v”一样长;这很糟糕,因为它甚至不会表现保证不好!)。通过省略parens,{{1}}与列表直接关联,并且仅在{{1}}被销毁时销毁支持数组数据。

答案 1 :(得分:5)

列表初始化依赖于知道正在初始化的类型。 {1}可能意味着许多事情。应用于int后,它会填充1.当应用于std::vector<int>时,表示创建一个元素vector,第一个元素1元件。等等。

当您调用模板函数时,其类型完全不受约束,则没有用于列表初始化的类型信息。如果没有类型信息,列表初始化将无法正常工作。

例如:

bar({{0,1}});

您希望它属于std::initializer_list<std::pair<int,int>>类型。但编译器怎么知道呢? bar的第一个参数是无约束模板;它可以是任何类型。编译器怎么可能猜到你的意思是这个特定类型?

很简单,它不能。编译器很好,但它们不是透视的。列表初始化只能在存在类型信息的情况下工作,而无约束模板会删除所有这些。

根据C ++ 11,根据所有权利,该行应该无法编译。它无法推断出您希望{...}的类型,因此它应该失败。这看起来像GCC的bug或其他什么。