关于c ++:为什么带括号的初始化程序的自动和模板类型推导有所不同?

Why do auto and template type deduction differ for braced initializers?

我知道,给定一个初始化初始化程序,auto将推导一种std::initializer_list类型,而模板类型推导将失败:

1
2
3
4
5
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:

[It's a non-deduced context if the program has] A function parameter for which the associated argument is an initializer list (8.5.4) but the parameter
does not have std::initializer_list or reference to possibly cv-qualified std::initializer_list
type. [ Example:

template void g(T);

g({1,2,3}); // error: no argument deduced for T

—end example ]

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

有人知道为什么auto会为支撑初始化程序推导类型,但是不允许模板吗? 尽管对"这可能是原因"形式的投机性解释很有趣,但我对那些知道为什么标准以这种方式编写的人特别感兴趣。


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

  • 有关将来的语言扩展的担忧(您可能会发明多种含义-如果我们想为括号式init列表函数自变量引入完美的转发,该怎么办?)

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

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

如果将T推导到initializer_list的右侧,但是将其推导到vector的左侧,则由于自相矛盾的论证推导而无法工作。

autoinitializer_list< T >的推论是有争议的。对于14之后的C ++,有一个建议将其删除(并禁止使用{ }{a, b}进行初始化,并使{a}推导为a的类型)。


原因在N2640中进行了描述:

A {}-list cannot deduce against a plain type parameter T. For example:

1
2
3
4
5
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).

Another example:

1
2
3
4
5
6
7
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.)

On the other hand, being able to deduce an initializer_list for T is attractive to
allow:

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

which was deemed desirable behavior since the very beginning of the EWG discussions about
initializer lists.

Rather than coming up with a clever deduction rule for a parameter type T matched with a {}-list (an option we pursued in earlier sketches and drafts of this paper), we now prefer to handle this with a special case for"auto" variable deduction when the initializer is a {}-list. I.e., for the specific case of a variable declared with an"auto" type specifier and a {}-list initializer, the"auto" is deduced as for a function f(initializer_list< T >) instead of as for a function f(T).

得出结论,问题在于,如果我们允许{} -list推断出普通类型的参数T,则具有参数T的函数在过载解析期间将具有非常高的优先级,这可能会导致有线行为(例如以上示例)。


首先,它是您所谓的"这可能是原因的"形式的"推测性解释"。

{1,2,3}不仅是std::initializer_list,而且允许没有构造函数的初始化类型。例如:

1
2
3
4
5
6
7
8
9
10
11
12
#include <initializer_list>

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

void f(x){

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

是正确的代码。为了表明它不是initializer_list,让我们看下面的代码:

1
2
3
4
5
6
7
8
9
10
11
#include <initializer_list>

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

void f(x){

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

错误是:

1
2
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的方法,并为其他类型添加了新功能后,对其稍有更改以调用完全不同的函数。