【问题标题】:Should template parameter T be resolving to T& when a function takes argument of T&& and is passed an lvalue? [duplicate]当函数接受 T&& 的参数并传递左值时,模板参数 T 是否应该解析为 T&? [复制]
【发布时间】:2015-01-19 15:56:26
【问题描述】:

我正在尝试用 C++ 进行完美转发,因此我编写了以下快速而肮脏的代码来测试这一点。

class CValue
{
public:

    CValue(int i) :
        m_i(i)
    {
        printf("Default constructor called!\r\n");
    }

    CValue(const CValue& src) :
        m_i(src.m_i)
    {
        printf("Copy constructor called!\r\n");
    }

    CValue(CValue&& src) :
        m_i(src.m_i)
    {
        printf("Move constructor called!\r\n");
    }

    CValue& operator=(const CValue& src)
    {
        m_i = src.m_i;
        printf("Copy assignment called!\r\n");
        return *this;
    }

    CValue& operator=(CValue&& src)
    {
        m_i = src.m_i;
        printf("Move assignment called!\r\n");
        return *this;
    }

    int     m_i;
};

template <typename T>
void PerfectForwarding(T&& tValue)
{
    T tValue1 = tValue;
    tValue1.m_i = 10;
}

int _tmain(int argc, _TCHAR* argv[])
{
    CValue v(0);
    PerfectForwarding(v);

    printf("%d\r\n", v.m_i);

    return 0;
}

当我将此代码作为控制台应用程序构建和运行时,我得到的答案是 10,而我期待的是 0。

好像是这样一行:

T tValue1 = tValue;

在PerfectForwarding函数中被解析为:

CValue& tValue1 = tValue;

代替:

CValue tValue1 = tValue;

所以编译器将 T 解析为 CValue&,这是我没想到的。

我尝试通过显式声明模板参数类型来从 _tmain 调用函数,即

PerfectForwarding<CValue>(v);

但是编译失败并出现以下错误:

error C2664: 'void PerfectForwarding<CValue>(T &&)' : cannot convert
              argument 1 from 'CValue' to 'CValue &&'
              with
              [
                  T=CValue
              ]
You cannot bind an lvalue to an rvalue reference

我可以通过将 PerfectForwarding 函数中的行更改为以下内容来强制执行所需的行为:

typename std::remove_reference<T>::type tValue1 = tValue;

但我认为这没有必要。通过引用折叠规则,参数 (T&&) 的类型应该变为 CValue&(如 T&& & -> T&),但 T 本身应该只是 CValue,确定吗?这是 VC12 编译器处理右值引用的错误,还是我误解了右值引用和模板?

我在调试中使用 Visual Studio 2013(VC12 编译器)并关闭所有优化。

【问题讨论】:

  • 在链接的欺骗中,专门扫描“通用”(或“转发”)引用。
  • @sehe,我认为stackoverflow.com/q/14302849/3093378 更适合骗子,即使就其标题而言也是如此。这个问题实际上与移动语义无关。
  • 参见Effective Modern C++ 第 1、24 和 28 项:...这是模板类型推导中唯一将 T 推导出为引用的情况
  • @jrok,是的,对不起,我期待的答案是 0,而不是 4。我已编辑问题以更正此问题。
  • @vsoftco 这看起来也很有帮助!您可以添加它(我认为?)(我认为该特定问题不会最终以自然查询来解释此 OP 所具有的这个问题的答案,但是是的,它的范围更简洁)

标签: c++ templates c++11 perfect-forwarding


【解决方案1】:

当编译器只看到T&amp;&amp; 时,它会尝试将T 推导出为允许用你给它的任何东西调用函数的东西。因此,当使用左值调用时,T 最终成为 Cvalue&amp;,因此引用折叠(正如您所指出的)可以启动。

当您尝试转发参数时,这很重要。 std::forward&lt;T&gt;(v) 将根据 T 的内容将参数作为左值或右值转发。如果它已经被推导出为左值引用,它将作为左值转发,如果没有,它将作为右值转发。 T 类型是唯一的区别。

根据引用折叠规则,参数 (T&&) 的类型应该变成 CValue&(如 T&& & -> T&),但 T 本身应该只是 CValue,确定吗?
如果用左值调用它会转到(T&amp;&amp; v)“好吧,我不能将右值引用绑定到左值,但如果我让T 本身成为左值引用,那么这行得通。” T 被推导出为 Cvalue&amp;,因此 (T&amp;&amp; v) 扩展为 (Cvalue&amp; &amp;&amp; v)。现在引用已折叠(Cvalue&amp; v)T 类型必须是左值引用类型才能使其工作。

如果您明确提供模板参数,那么您并没有真正解决问题。解决此问题的第一种方法是您找到的remove_reference。你也可以使用auto,这更有意义,因为这是通用编程

auto tValue1 = tValue;

在任何情况下,auto 都不会推断为引用。对于您提供的内容,最好使用可以绑定到左值和右值的const 左值引用。

template <typename T>
void PerfectForwarding(const T& tValue)
{
    T tValue1 = tValue;
    tValue1.m_i = 10;
}

这不允许转发,但无论如何它都不是转发功能。

如果你想以不同的方式处理左值,你可以提供两个重载

template <typename T>
void PerfectForwarding(T& tValue);

template <typename T>
void PerfectForwarding(T&& tValue);

当使用左值调用时,前者将是首选。

【讨论】:

  • 虽然应该提到auto&amp;&amp;auto的完美转发版!
  • @OmnipotentEntity:那decltype(auto)是什么?
  • @BenVoigt 无论 rhs 被宣布为什么。
  • @BenVoigt auto 通过模板参数推导规则推导表达式的类型,decltype(auto) 通过decltype() 推导。
  • @BenVoigt 在转发的情况下,难道真的只是为了获得对对象的正确引用吗?转发函数总是绑定一个引用,它不仅在函数的参数被声明为引用的情况下绑定。
【解决方案2】:

行为是正确的。当参数为左值时,通过推导类型的左值引用来实现完美转发。

你说的是

根据引用折叠规则,参数的类型(T&amp;&amp;)应该变成CValue&amp;(如T&amp;&amp; &amp; -> T&amp;),但T 本身应该只是CValue,确定吗?

但是如果T 只是CValue,那么T&amp;&amp; &amp; 中的&amp; 又会从何而来? T&amp;&amp; 用作转发引用的全部原因是T 成为左值引用,在您的情况下为CValue&amp;,然后引用折叠得到T &amp;&amp; -> CValue &amp; &amp;&amp; -> CValue &amp;

标准的相关部分是C++11 14.8.2.1/3(P是函数模板参数类型,A是调用中的参数类型,定义在14.8.2.1/1 ):

如果 P 是 cv 限定类型,则在类型推导中忽略 P 类型的顶级 cv 限定符。如果P 是 引用类型,P引用的类型用于类型推导。 如果P 是对不合格 cv 的右值引用 模板形参且实参为左值,类型“lvalue reference to A”用于 A 用于类型推导的地方。

(强调我的)

【讨论】:

    【解决方案3】:

    编译器做正确的事。在一行像

    template<typename T>
    void f(T&& param){}
    

    T 如果您传递左值,则被推断为引用,如果您传递右值,则被推断为简单类型。所以int x; f(x);T 推导出为int&amp;,而f(1)T 推导为int

    这就是完美转发的工作原理。基本上,您有以下参考折叠规则:

    & &   -> &
    & &&  -> &
    && &  -> &
    && && -> &&
    

    另见

    Syntax for universal references

    了解更多详情。

    【讨论】:

      猜你喜欢
      • 2012-01-21
      • 2010-09-16
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2015-08-21
      相关资源
      最近更新 更多