我试图理解在组合初始化列表和const auto
时C ++ 11的正确行为。我在GCC和Clang之间获得了以下代码的不同行为,并想知道哪个是正确的:
#include <iostream>
#include <typeinfo>
#include <vector>
int main()
{
const std::initializer_list<int> l1 = { 1, 2, 3 };
const auto l2 = { 1, 2, 3 };
std::cout << "explicit: " << typeid(l1).name() << std::endl;
std::cout << "auto: " << typeid(l2).name() << std::endl;
}
使用g ++编译输出为:
explicit: St16initializer_listIiE
auto: St16initializer_listIKiE
虽然clang ++编译版本产生:
explicit: St16initializer_listIiE
auto: St16initializer_listIiE
似乎GCC正在将auto
行转换为std::initializer_list<const int>
,而Clang会生成std::initializer_list<int>
。当我使用它来初始化std::vector
时,GCC版本会产生问题。因此,以下在Clang下工作,但会为GCC产生编译器错误。
// Compiles under clang but fails for GCC because l4
std::vector<int> v2 { l2 };
如果GCC正在生成正确的版本,那么它似乎建议应该扩展各种STL容器以包含这些情况的另一个列表初始化程序重载。
注意:这种行为在GCC(4.8,4.9,5.2)和Clang(3.4和3.6)的多个版本中似乎是一致的。
答案 0 :(得分:10)
GCC错误。 [dcl.spec.auto] / p7(引用N4527):
初始化使用占位符类型声明的变量时 [...]推导出的返回类型或变量类型由 其初始化程序的类型。 [...]否则,让
T
成为声明的类型 变量[...]。如果占位符是auto
type-specifier ,推导出的类型是使用模板参数推导的规则确定的。如果初始化是 直接列表初始化 [...]。 [...]否则,通过将P
的出现替换为新发明的T
,从auto
获取U
类型模板参数std::initializer_list<U>
或者,如果初始化是 copy-list-initialization ,U
。使用a中的模板参数推导规则推导P
的值 函数调用(14.8.2.1),其中U
是函数模板参数 type和相应的参数是初始化器[...]。如果 扣除失败,声明形成不良。否则,类型 通过替换获得的变量或返回类型的推导 推导出P
到const auto l2 = { 1, 2, 3 };
。
因此,在template<class U> void meow(const std::initializer_list<U>);
中,对函数模板执行推导
meow({1, 2, 3})
给出电话auto l3 = { 1, 2, 3 };
。
现在考虑无常数std::initializer_list<int>
(GCC正确推导为template<class U> void purr(std::initializer_list<U>);
)。这种情况下的扣除就像功能模板
purr({1, 2, 3})
给出电话P
。
由于忽略了函数参数的顶级cv限定,显然两个推论应该产生相同的类型。
[temp.deduct.call] / P1:
通过比较每个函数来完成模板参数推导 模板参数类型(称之为
A
)的类型 相应的调用参数(称之为P
)如下所述。 如果P
是依赖类型,则从中删除引用和cv限定符std::initializer_list<P'>
为某些P'
[...]提供了P'
[...] 参数是一个非空的初始化列表(8.5.4),然后是演绎 取而代之的是执行初始化列表的每个元素P'
作为函数模板参数类型和初始化元素 作为其论点。
对U
,1
或2
推导3
(int
),int
类型的所有文字,显然都会产生list
。
答案 1 :(得分:6)
有一个关于此案例和类似案例的gcc错误报告wrong auto deduction from braced-init-list,理查德史密斯表示这是一个gcc错误:
更简单的测试用例:
#include <initializer_list> const auto r = { 1, 2, 3 }; using X = decltype(r); using X = const std::initializer_list<int>;
失败,因为
decltype(r)
被推断为const std::initializer_list<const int>
而不是const std::initializer_list<int>
。
C ++标准草案的部分是7.1.6.4
[dcl.spec.auto] 部分,其中说:
初始化使用占位符类型声明的变量,或者在函数中发生return语句 声明的返回类型包含占位符类型,推导的返回类型或变量类型 根据其初始化程序的类型确定。 [...]设T是函数的变量或返回类型的声明类型。如果 占位符是自动类型说明符,推导的类型是使用模板参数的规则确定的 扣除。 [...]否则,通过用a替换auto的出现来从T获得P. 新发明的类型模板参数U或者,如果初始化程序是braced-init-list,则使用std :: initializer_- 名单。使用函数调用中的模板参数推导规则(14.8.2.1)为U推导一个值, 其中P是函数模板参数类型,初始值设定项是相应的参数[...] [例如:
auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int> auto x2 = { 1, 2.0 }; // error: cannot deduce element type
-end example] [例如:
const auto &i = expr;
i的类型是以下发明的函数模板的调用f(expr)中参数u的推导类型:
template <class U> void f(const U& u);
-end example]