为什么自动初始值设定项的自动和模板类型推导会有所不同?

时间:2013-07-10 23:29:37

标签: c++ c++11 templates template-deduction list-initialization

我理解,如果使用支持的初始值设定项,auto将推导出std::initializer_list类型,而模板类型推导将失败:

auto var = { 1, 2, 3 };   // type deduced as std::initializer_list<int>

template<class T> void f(T parameter);

f({ 1, 2, 3 });          // doesn't compile; type deduction fails

我甚至知道在C ++ 11标准中指定的位置:14.8.2.5/5 bullet 5:

  

[如果程序有,这是一个非推导的上下文]一个函数参数,其关联参数是初始化列表(8.5.4)但参数   没有std :: initializer_list或对可能是cv-qualified的std :: initializer_list的引用   类型。 [示例:

     

模板void g(T);

     

克({1,2,3}); //错误:没有推断出T

的参数      

- 结束示例]

我不知道或理解的是为什么存在类型演绎行为的这种差异。 C ++ 14 CD中的规范与C ++ 11中的规范相同,因此标准化委员会可能不会将C ++ 11行为视为缺陷。

有人知道为什么auto推断出支撑初始值设定项的类型,但是不允许使用模板吗?虽然对“这可能是原因”形式的推测性解释很有意思,但我特别感兴趣的是那些知道为什么标准是按原样编写的人的解释。

3 个答案:

答案 0 :(得分:13)

模板不做任何演绎有两个重要原因(我在与负责人的讨论中记得两个)

  • 对未来语言扩展的担忧(你可以发明多种含义 - 如果我们想要为支持的初始化列表函数参数引入完美的转发呢?)

  • 大括号有时可以有效地初始化依赖的函数参数

template<typename T>
void assign(T &d, const T& s);
int main() {
  vector<int> v;
  assign(v, { 1, 2, 3 });
}

如果T在右侧推导到initializer_list<int>但在左侧推导到vector<int>,则由于矛盾的参数推断而无法推断。

autoinitializer_list<T>的扣除是有争议的。存在针对C ++的提议 - 在14之后删除它(并禁止使用{ }{a, b}进行初始化,并使{a}推导为a的类型)

答案 1 :(得分:1)

原因在N2640中进行了描述:

  

{}列表无法推导纯类型参数T。例如:

template<class T> void count(T); // (1).
struct Dimensions { Dimensions(int, int); };
size_t count(Dimensions); // (2).
size_t n = count({1, 2}); // Calls (2); deduction doesn't
                          // succeed for (1).
     

另一个例子:

template<class T>
void inc(T, int); // (1)
template<class T>
void inc(std::initializer_list<T>, long); // (2)
inc({1, 2, 3}, 3); // Calls (2). (If deduction had succeeded
                   // for (1), (1) would have been called — a
                   // surprise.)
     

另一方面,能够为initializer_list<X>推论T对于   允许:

auto x = { 1, 1, 2, 3, 5 };
f(x);
g(x);
     

自从EWG关于   初始化程序列表。

     

我们现在没有为参数类型T和{}列表(我们在本文前面的草图和草稿中追求的一种选择)匹配的巧妙的推导规则,而是选择使用当初始化程序为{} -list时,“自动”变量推导的一种特殊情况。即,对于使用“自动”类型说明符和{} -list初始化程序声明的变量的特定情况,“自动”推导是针对函数f(initializer_list<T>)而不是针对函数{{1} }。

总而言之,问题在于,如果我们允许{} -list推断出普通类型参数f(T),那么在重载解析期间,带有参数T的函数将具有很高的优先级,可能会导致有线行为(如上述示例)。

答案 2 :(得分:0)

首先,它是“形式的推测性解释”,这可能就是你所说的“”原因。

{1,2,3}不仅是std::initializer_list<int>,还允许在没有构造函数的情况下初始化类型。例如:

#include <initializer_list>

struct x{
    int a,b,c;
};

void f(x){

}
int main() {
    f({1,2,3});
}

是正确的代码。要表明它不是initializer_list,请看下面的代码:

#include <initializer_list>

struct x{int a,b,c;};

void f(x){

}
int main() {
    auto il = {1, 2, 3};
    f(il);
}

错误是:

prog.cpp: In function ‘int main()’:
prog.cpp:10:9: error: could not convert ‘il’ from ‘std::initializer_list<int>’ to ‘x’

现在问题是“有什么区别?”

auto x = {1, 2, 3};代码中确定类型是可以的,因为编码器使用auto

明确地说“它的类型并不重要”

在功能模板的情况下,他可能确定他使用的是不同的类型。并且防止模糊情况下的错误是好的(它看起来不像C ++风格)。

特别糟糕的是,当有1个函数f(x)然后它被更改为模板1时。程序员写道将其用作x,并在为其他类型添加新函数后稍微改变以调用完全不同的函数。