我正试着用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编译器),关闭了所有优化。
答案 0 :(得分:3)
当编译器只看到T&&
时,它会尝试将T
推断为允许使用您提供的任何内容调用函数的内容。因此,当使用左值调用T
时Cvalue&
为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)
推导T
为int
。
这是多么完美的转发工作。基本上,您有以下参考折叠规则:
& & -> &
& && -> &
&& & -> &
&& && -> &&
另见
Syntax for universal references
了解更多详情。