【问题标题】:const auto std::initializer_list difference between Clang and GCCClang 和 GCC 之间的 const auto std::initializer_list 区别
【发布时间】:2015-12-07 12:37:21
【问题描述】:

我试图了解结合初始化列表和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&lt;const int&gt; 而 Clang 生成 std::initializer_list&lt;int&gt;。当我使用 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)的多个版本中似乎是一致的。

【问题讨论】:

  • 你能检查std::vector&lt;int&gt; v2 ( l2.begin(), l2.end() );(它应该可以工作)考虑到这一点,也许应该接受任何类型的隐式转换的初始化列表,即使推导发生了变化。另一方面,这可能会导致初始化列表构造函数更频繁地隐藏其他构造函数。
  • 是`std::vector v2 (l2.begin(), l2.end());确实有效。

标签: c++ c++11 gcc stl clang


【解决方案1】:

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

当使用占位符类型声明的变量被初始化时, [...] 推导的返回类型或变量类型由 其初始化程序的类型。 [...] 否则,让 T 成为声明的类型 的变量[...]。如果占位符是auto type-specifier,推导的类型是使用模板参数推导规则确定的。如果初始化是 直接列表初始化 [...]。 [...] 否则,通过将出现的 auto 替换为新发明的任意一种,从 T 获取 P 类型模板参数U 或者,如果初始化是 复制列表初始化,使用std::initializer_list&lt;U&gt;。使用模板参数推导规则从一个推导 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})

现在考虑无常量情况auto l3 = { 1, 2, 3 };(GCC 正确推断为std::initializer_list&lt;int&gt;)。在这种情况下进行推演,就像对函数模板一样

template<class U> void purr(std::initializer_list<U>);

鉴于电话purr({1, 2, 3})

由于函数参数的顶级 cv 限定被忽略,显然这两个推导应该产生相同的类型。


[temp.deduct.call]/p1:

模板参数推导是通过比较每个函数来完成的 模板参数类型(称为P)与 调用的相应参数(称为A),如下所述。 如果P 是依赖类型,则从 Pstd::initializer_list&lt;P'&gt; [...] 一些P' [...] 和 参数是一个非空的初始化列表(8.5.4),然后推导 而是对初始化列表的每个元素执行,取 P' 作为函数模板参数类型和初始化元素 作为它的论据。

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

【讨论】:

  • 我看不出这里的 顶级 cv-qualification 有什么问题,问题是 initializer_list&lt;const int&gt; 而不是 const initializer_list&lt;int&gt;
  • auto l3 = { 1, 2, 3 }; 不会在 gcc 中推导出相同的模板参数(const int)吗?
  • 正确的auto l3 = { 1,2,3}; 不会推导出与 gcc 中的const auto l2 = {1,2,3}; 相同的模板参数。当我尝试typeid(l3).name() 时会产生St16initializer_listIiE
  • @T.C.感谢您的快速回复。
【解决方案2】:

有一个 gcc 错误报告 wrong auto deduction from braced-init-list 关于这个和类似的情况,Richard Smith 指出这是一个 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&lt;const int&gt; 而不是const std::initializer_list&lt;int&gt;

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

当使用占位符类型声明的变量被初始化,或函数中出现return语句时 使用包含占位符类型、推导的返回类型或变量类型的返回类型声明 由其初始化程序的类型确定。 [...] 令 T 为变量的声明类型或函数的返回类型。如果 占位符是自动类型说明符,推导的类型是使用模板参数的规则确定的 扣除。 [...] 否则,通过将出现的 auto 替换为 a 新发明的类型模板参数 U,或者,如果初始化程序是一个花括号初始化列表,则使用 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

—结束示例] [示例:

const auto &i = expr;

i 的类型是以下发明函数模板的调用 f(expr) 中参数 u 的推导类型:

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

——结束示例]

【讨论】:

  • 感谢您的快速响应和错误报告链接。
猜你喜欢
  • 2016-08-02
  • 2016-08-01
  • 2021-12-28
  • 2017-08-15
  • 1970-01-01
  • 2013-12-30
  • 2015-02-06
  • 1970-01-01
相关资源
最近更新 更多