模板参数T应该解析为T&当一个函数接受T&&的论证时并传递左值?

时间:2015-01-19 15:56:26

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

我正试着用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&amp;,这是我没想到的。

我尝试通过显式声明模板参数类型来调用_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&amp;&amp;)应该变成CValue&amp; (作为T&amp;&amp;&amp; - &gt; T&amp;),但T本身应该只是简单的CValue,当然?这是VC12编译器处理rvalue引用时的错误,还是我误解了rvalue引用和模板?

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

3 个答案:

答案 0 :(得分:3)

当编译器只看到T&&时,它会尝试将T推断为允许使用您提供的任何内容调用函数的内容。因此,当使用左值调用TCvalue&std::forward<T>(v),因此参考折叠(正如您所指出的那样)可以启动。

当你试图转发争论时,这很重要。 T会将参数转发为左值或右值,具体取决于T。如果它被推导为左值引用,它会将其转发为左值,如果它没有,它将作为右值转发。类型(T&& v)是区别的唯一因素。

通过引用折叠规则,参数的类型(T&amp;&amp;)应该变为CValue&amp; (作为T&amp;&amp;&amp; - &gt; T&amp;),但T本身应该只是CValue,当然?
如果用左值调用它会T&#34;好吧,我不能将左值引用绑定到左值,但是如果我使T本身成为左值引用,那么这是有效的。 &#34; Cvalue&被推断为(T&& v),以便(Cvalue& && v)扩展为(Cvalue& 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);

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

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

{{1}}

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

答案 1 :(得分:1)

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

你在说

  

通过引用折叠规则,参数的类型(T&&)应该变为CValue&T&& & - > T&},但是T本身应该只是CValue,当然?

但如果&只是T&& &T中的CValue会来自哪里? T&&作为转发引用的完整原因是T成为左值引用,在您的情况下为CValue&,然后引用折叠获取T && - &gt; CValue & && - &gt; CValue &

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

  

如果P是cv限定类型,则P类型的顶级cv限定符将被忽略以进行类型推导。如果P是   引用类型,P引用的类型用于类型推导。 如果P是对cv-nonqualified的右值引用   模板参数和参数是左值,使用类型“A的左值引用”   A的地方类型扣除。

(强调我的)

答案 2 :(得分:0)

编译器做对了。在像

这样的行中
template<typename T>
void f(T&& param){}
如果传递左值,则

T被推导为参考,如果传递右值,则推导出简单类型。因此int x; f(x);T推断为int&f(1)推导Tint

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

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

另见

Syntax for universal references

了解更多详情。