【问题标题】:Why do auto and template type deduction differ for braced initializers?为什么大括号初始值设定项的自动和模板类型推导不同?
【发布时间】:2013-07-09 02:13:30
【问题描述】:

我知道,给定一个大括号初始化器,auto 将推导出 std::initializer_list 的类型,而模板类型推导将失败:

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:

[如果程序有,这是一个非推导上下文]一个函数参数,其关联参数是一个初始化列表(8.5.4),但参数 没有 std::initializer_list 或对可能 cv 限定的 std::initializer_list 的引用 类型。 [ 例子:

模板 void g(T);

g({1,2,3}); // 错误:没有为 T 推导出参数

结束示例 ]

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

有谁知道为什么auto 为花括号初始化器推导出一个类型,但不允许模板这样做?虽然“这可能是原因”形式的推测性解释很有趣,但我对那些知道标准为何如此编写的人的解释特别感兴趣。

【问题讨论】:

  • 最近有人问了另一个关于你发现了什么的问题。它可能有一些原因。
  • 好的,找到了。唯一真正的为什么讨论是一条评论,但你可以看看:stackoverflow.com/questions/17496268/…
  • 引自Scott Meyers:“我不知道为什么auto 和模板的类型推导不一样。如果你知道,请告诉我!”。

标签: c++ c++11 templates template-argument-deduction list-initialization


【解决方案1】:

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

  • 对未来语言扩展的担忧(您可以发明多种含义 - 如果我们想为大括号 init 列表函数参数引入完美转发呢?)

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

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

如果T 将在右侧推导出为initializer_list&lt;int&gt;,但在左侧推导为vector&lt;int&gt;,则由于自相矛盾的论点推导,这将无法工作。

autoinitializer_list&lt;T&gt; 的扣除是有争议的。有一个建议 C++-after-14 将其删除(并禁止使用 { }{a, b} 进行初始化,并使 {a} 推断为 a 的类型)。

【讨论】:

    【解决方案2】:

    原因在N2640中描述:

    {} 列表无法针对普通类型参数 T 进行推断。例如:

    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).
    

    另一个例子:

    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.)
    

    另一方面,能够为T 推断出initializer_list&lt;X&gt; 对 允许:

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

    自 EWG 讨论一开始就被认为是可取的行为 初始化列表。

    与其为与 {}-list 匹配的参数类型 T 提出一个巧妙的推导规则(我们在本文的早期草图和草稿中采用的一个选项),我们现在更愿意用一个特殊的方法来处理这个问题当初始值设定项是 {} 列表时“自动”变量推导的情况。即,对于使用“auto”类型说明符和 {}-list 初始化程序声明的变量的特定情况,“auto”被推断为函数 f(initializer_list&lt;T&gt;) 而不是函数 f(T)

    总而言之,问题在于,如果我们允许 {}-list 对普通类型参数 T 进行推导,那么带有参数 T 的函数在重载解析期间将具有非常高的优先级,这可能会导致连线行为(如上面的示例)。

    【讨论】:

      【解决方案3】:

      首先,它是“对“这可能是原因”形式的推测性解释”。

      {1,2,3} 不仅是std::initializer_list&lt;int&gt;,还允许在没有构造函数的情况下初始化类型。例如:

      #include <initializer_list>
      
      struct x{
          int a,b,c;
      };
      
      void f(x){
      
      }
      int main() {
          f({1,2,3});
      }
      

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

      #include <initializer_list>
      
      struct x{int a,b,c;};
      
      void f(x){
      
      }
      int main() {
          auto il = {1, 2, 3};
          f(il);
      }
      

      错误是:

      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,并在为其他类型添加新函数后稍作更改以调用完全不同的函数。

      【讨论】:

      • 我不明白 auto 的意思是“类型是什么并不重要”,但 template&lt;typename T&gt; 的意思是不同的。在这两种情况下,您都冒着推断出的类型可能不是程序员想要的风险。
      • 我不明白第二个代码。您写“显示它不是初始化列表”,但在示例中 {1,2,3} 是初始化列表,因此错误...
      • 我的意思是我在第一个代码中表明它不是 initializer_list。因为当它是 initialzer_list 时它不起作用
      猜你喜欢
      • 2015-06-16
      • 2017-03-07
      • 2011-05-31
      • 2018-03-06
      • 2022-11-20
      • 1970-01-01
      • 2023-03-11
      相关资源
      最近更新 更多