const auto std::initializer_list Clang与GCC的差异



我试图理解c++ 11在组合初始化列表和const auto时的正确行为应该是什么。对于以下代码,我在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>生产线。当我使用GCC版本初始化std::vector时,它会产生一个问题。所以下面的代码可以在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)中是一致的。

GCC bug。[dcl.spec。auto]/p7(引用N4527):

当使用占位符类型声明的变量初始化时,[…的返回类型或变量类型是由其初始化项的类型。[…否则,让T作为声明的类型变量[…]。如果占位符是auto类型说明符,推导的类型是使用模板参数推导的规则确定的。如果初始化是 direct-list-initialization […]。[…否则,通过将auto的出现替换为一个新发明的T来获得P类型模板参数U或,如果初始化是copy-list-initializationstd::initializer_list<U>。使用模板参数从a中推导出U的值的规则函数调用(14.8.2.1),其中P是函数模板形参类型和对应的实参是初始化式[…]。如果演绎失败,声明格式错误。否则,键入为变量或返回类型推导,通过代入得到将U推导为P

因此,在const auto l2 = { 1, 2, 3 };中,就像对函数模板

执行演绎一样
template<class U> void meow(const std::initializer_list<U>);

给定调用meow({1, 2, 3})

现在考虑无const的情况auto l3 = { 1, 2, 3 }; (GCC正确地推断为std::initializer_list<int>)。在这种情况下,执行演绎就像对函数模板

执行演绎一样
template<class U> void purr(std::initializer_list<U>);

给定调用purr({1, 2, 3}) .

由于忽略了函数参数的顶层cv-限定,因此很明显,这两种演绎应该产生相同的类型。


[temp.deduct.call]/p1:

通过比较每个函数来推导模板实参模板参数类型(称为P)的类型调用的对应参数(称之为A),如下所述。如果P是依赖类型,从P给出std::initializer_list<P'>[…]对于一些P'[…]),实参是一个非空的初始化列表(8.5.4),然后是演绎而对初始化列表的每个元素执行P'作为函数模板形参类型和初始化元素

P'(即U)与123(所有类型为int的字量)进行比较,显然得到int

关于这个和类似的情况,有一个gcc bug报告错误地自动从braced-init-list中扣除,Richard Smith指出这是一个gcc bug:

更简单的测试用例:

#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]显示:

使用占位符类型声明的变量初始化时,或函数中出现返回语句时使用包含占位符类型、推导的返回类型或变量类型的返回类型声明由其初始化项的类型确定。[…设T为变量的声明类型或函数的返回类型。如果占位符是自动类型说明符,推导的类型是使用模板参数的规则确定的扣除。[…否则,用a代替auto的出现,从T中得到P新发明的类型模板形参U或,如果初始化器是带括号的初始化列表,则使用std::initializer_-列表。使用从函数调用中推导模板参数的规则来推导U的值(14.8.2.1),其中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]

最新更新