const auto std::initializer_list Clang 和 GCC 的区别

const auto std::initializer_list difference between Clang and GCC

我试图了解在组合初始化列表和 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>。当我使用 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 错误报告 wrong auto deduction from braced-init-list,Richard Smith 指出这是一个 gcc 错误:

Even simpler testcase:

#include <initializer_list>
const auto r = { 1, 2, 3 };
using X = decltype(r);
using X = const std::initializer_list<int>;

fails because decltype(r) is deduced as const std::initializer_list<const int> rather than const std::initializer_list<int>.

C++ 标准草案的部分是 7.1.6.4 [dcl.spec.auto] 部分,它说:

When a variable declared using a placeholder type is initialized, or a return statement occurs in a function declared with a return type that contains a placeholder type, the deduced return type or variable type is determined from the type of its initializer. [...] Let T be the declared type of the variable or return type of the function. If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction. [...] Otherwise, obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initializer is a braced-init-list, with std::initializer_- list. Deduce a value for U using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the initializer is the corresponding argument [...] [ Example:

auto x1 = { 1, 2 }; // decltype(x1) is std::initializer_list<int>
auto x2 = { 1, 2.0 }; // error: cannot deduce element type

—end example ] [ Example:

const auto &i = expr;

The type of i is the deduced type of the parameter u in the call f(expr) of the following invented function template:

template <class U> void f(const U& u);

—end example ]

海湾合作委员会错误。 [dcl.spec.auto]/p7(引用 N4527):

When a variable declared using a placeholder type is initialized, [...] the deduced return type or variable type is determined from the type of its initializer. [...] Otherwise, let T be the declared type of the variable [...]. If the placeholder is the auto type-specifier, the deduced type is determined using the rules for template argument deduction. If the initialization is direct-list-initialization [...]. [...] Otherwise, obtain P from T by replacing the occurrences of auto with either a new invented type template parameter U or, if the initialization is copy-list-initialization, with std::initializer_list<U>. Deduce a value for U using the rules of template argument deduction from a function call (14.8.2.1), where P is a function template parameter type and the corresponding argument is the initializer [...]. If the deduction fails, the declaration is ill-formed. Otherwise, the type deduced for the variable or return type is obtained by substituting the deduced U into 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:

Template argument deduction is done by comparing each function template parameter type (call it P) with the type of the corresponding argument of the call (call it A) as described below. If P is a dependent type, removing references and cv-qualifiers from P gives std::initializer_list<P'> [...] for some P' [...] and the argument is a non-empty initializer list (8.5.4), then deduction is performed instead for each element of the initializer list, taking P' as a function template parameter type and the initializer element as its argument.

根据 123 推导 P'(即 U),类型为 int 的所有文字,显然会产生 int.