【问题标题】:When you perfect-forward, typename T becomes a T& or T&&, but when you don't, T isn't a reference at all. How?当你完美前移时,类型名 T 变为 T& 或 T&&,但当你不这样做时,T 根本不是引用。如何?
【发布时间】:2013-04-24 13:05:06
【问题描述】:

我正在阅读有关完美转发的文章,这是我学到的让我感到困惑的东西:
当你试图实现完美转发时,你会做这样的事情:

template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T &&x);  // like this: void foo(int& &&x)

然后我想,等等,这是否意味着如果我这样做了:

template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T x);    // like this: void foo(int& x);

但事实并非如此。 foo 看起来像这样:void foo(int x);

我的问题:为什么在完美的转发函数中,T 变成了 T& 或 T&&,但在另一个中,T 不是引用?有人能告诉我这个的确切规则吗?我需要澄清一下!

【问题讨论】:

  • 当T&amp;&amp; 可以成为左值或右值引用时,Scott Meyers 将其称为“通用引用”。 (我觉得这个术语越来越多地被专家采用。)我建议你看看thispresentaion,并阅读他在Overload 111的文章。

标签: c++ templates c++11


【解决方案1】:

如果模板参数T 出现在T&amp;&amp; 形式的函数参数中,则仅可以将其推断为引用类型

表单的函数模板:

  • template&lt;class T&gt; void f(T x)
    会将T 推断为对象类型(而x 是对象类型,因此按值传递)
  • template&lt;class T&gt; void f(T&amp; x)
    会将T 推导出为对象类型(然后x 具有左值引用类型)
  • template&lt;class T&gt; void f(T&amp;&amp; x)
    将推导出 T 为
    • 或者一个左值引用(所以x由于引用折叠规则而具有左值引用类型)
    • 或作为对象类型(所以x 具有右值引用类型)

完美的转发功能怎么来的,T变成T&还是T&&,[...]

这是错误的。 T 成为引用类型L&amp; 或对象类型R,不是引用R&amp;&amp;。
T&amp;&amp; 形式的函数参数因此变为

  • 要么L&amp;(因为将右值引用添加到左值引用仍然是左值引用,就像add_rvalue_reference&lt;L&amp;&gt;::type 仍然是L&amp;)
  • 或者变成R&amp;&amp;(因为add_rvalue_reference&lt;R&gt;::type是R&amp;&amp;)

【讨论】:

  • 你给了(恕我直言)最容易理解的答案,所以我接受你的。感谢您为我解决这个问题。
【解决方案2】:

这是因为类型推导的定义方式,仅在传递右值引用时std::forward&lt;&gt;()的结果为右值的意义上与完美转发有关如果传递了左值引用,则为左值。

但一般来说,当您没有引用开始时,您的T 不会被推断为引用类型(即A&amp;,无论A可能)。如果是这样的话,正如Yakk 在 cmets 中正确指出的那样,编写一个按值获取参数的函数模板是不可能的。

特别是,您所指的引用折叠规则在 C++11 标准的第 14.8.2.1/4 段中定义:

如果 P 是 引用类型,P引用的类型用于类型推导。 如果 P 是对 cv 不合格的右值引用 模板形参和实参是左值,类型“lvalue reference to A”用于 类型推导A的地方。 [例子:

template <class T> int f(T&&);
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue

——结束示例 ]

【讨论】:

  • 为什么也有用。如果template&lt;typename T&gt; void foo(T t) 可以推导出T=int&amp;,那么就不可能推导出一个按值获取参数的模板函数,因为总是存在一个有效的引用类型(T&amp;&amp;、T&amp;、T const&amp; 或@987654332 @) 用于任何参数。另一方面,将T&amp;&amp; 中的T 推导出为引用类型的能力使得完美转发成为可能,而无需每次都编写4 个函数。但是,编写一个将任何类型作为右值引用的函数确实很棘手。
  • @Yakk:确实如此。我将参考您的评论来提及这一点。谢谢
【解决方案3】:

这样推导模板参数时需要考虑三种一般情况。

  1. void foo(T x):这意味着“按值传递”。它总是推导出一个适当的类型以通过值传递。

  2. void foo(T&amp; x):这意味着“通过左值引用”。它总是推断出适当的类型以通过左值引用传递。

  3. void foo(T&amp;&amp; x):这意味着“通过引用”。它总是推断出适合通过引用传递的类型,可以是左值引用或右值引用。

【讨论】:

    【解决方案4】:

    放松,放慢速度,呼吸。

    模板参数推导是您需要了解的核心机制,而且它并非完全无关紧要。当您说template &lt;typename T&gt; void foo(T) 时,T总是被推断为非引用类型。

    如果你想要一个引用,你必须在上面加上一个&amp;:template &lt;typename T&gt; void foo(T&amp;) 也会将T 推导出为非引用类型,但foo 现在总是需要一个左值引用。

    最后的魔法来自新的参考折叠规则。当你说template &lt;typename T&gt; void foo(T&amp;&amp;) 时,可能会发生两件事:

    • 您使用右值调用foo,例如foo(Bar())。然后T被推导出为Bar,foo对Bar进行右值引用,即Bar&amp;&amp;。

    • 你用左值调用foo,例如Bar x; foo(x);。现在foo 唯一可以接受的是左值引用。这需要将T 推导出为Bar&amp;,因为T&amp;&amp; == Bar&amp; &amp;&amp; == Bar&amp;,由于折叠规则。

    只有这个最终模板能够同时接受左值和右值。这就是为什么它有时被称为“通用参考”的原因;但请记住,重要的不是引用,而是模板参数推导。使用std::forward&lt;T&gt; 允许您传递具有与您收到的相同值类别的参数。

    【讨论】:

      猜你喜欢
      • 2015-04-24
      • 1970-01-01
      • 2013-02-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-02-18
      • 1970-01-01
      • 2010-10-21
      相关资源
      最近更新 更多