我刚刚对那些(相当)新功能进行了一些研究,我想知道为什么C++委员会决定为它们引入相同的语法?似乎开发人员不必浪费一些时间来理解它是如何工作的,并且一个解决方案让我们考虑进一步的问题.在我的情况下,它从问题开始,可以简化为:
#include <iostream>
template <typename T>
void f(T& a)
{
std::cout << "f(T& a) for lvalues\n";
}
template <typename T>
void f(T&& a)
{
std::cout << "f(T&& a) for rvalues\n";
}
int main()
{
int a;
f(a);
f(int());
return 0;
}
Run Code Online (Sandbox Code Playgroud)
我首先在VS2013上编译它,它按照我的预期工作,结果如下:
f(T& a) for lvalues
f(T&& a) for rvalues
Run Code Online (Sandbox Code Playgroud)
但有一个可疑的事情:intellisense强调了f(a).我做了一些研究,我明白这是因为类型崩溃(Scott Meyers将它命名为通用引用),所以我想知道g ++对它的看法.当然它没有编译.微软实现他们的编译器以更直观的方式工作是非常好的,但我不确定它是否符合标准,是否应该在IDE中存在这种差异(编译器与智能感知,但实际上可能存在在某种程度上是有道理的).好的,回到问题所在.我用这种方式解决了它:
template <typename T>
void f(T& a)
{
std::cout << "f(T& a) for lvalues\n";
}
template <typename T>
void f(const T&& a)
{
std::cout << "f(T&& a) for …Run Code Online (Sandbox Code Playgroud) 最常见的用法std::forward是,完美地转发转发(通用)引用,例如
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
Run Code Online (Sandbox Code Playgroud)
这param是一个lvalue,并std::forward最终将它转换为右值或左值,具体取决于与它有关的参数.
看一下cppreference.com的定义,std::forward我发现还有一个rvalue重载
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
Run Code Online (Sandbox Code Playgroud)
任何人都可以给我任何rvalue超载的理由吗?我看不到任何用例.如果你想将一个右值传递给一个函数,你可以按原样传递它,不需要std::forward在它上面应用.
这与std::move我不同,我明白为什么人们也想要一个rvalue重载:你可能会处理通用代码,在这些代码中你不知道你传递了什么,你想要无条件支持移动语义,请参阅例如为什么std ::移动采取普遍参考?.
在VS2010中,std :: forward定义如下:
template<class _Ty> inline
_Ty&& forward(typename identity<_Ty>::type& _Arg)
{ // forward _Arg, given explicitly specified type parameter
return ((_Ty&&)_Arg);
}
Run Code Online (Sandbox Code Playgroud)
identity似乎仅用于禁用模板参数推断.在这种情况下故意禁用它有什么意义?
我正在尝试完美转发,发现
std::forward()需要两个重载:
过载 1:
template <typename T>
inline T&& forward(typename
std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
Run Code Online (Sandbox Code Playgroud)
重载nr.2:
template <typename T>
inline T&& forward(typename
std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
Run Code Online (Sandbox Code Playgroud)
现在,完美转发的典型场景是
template <typename T>
void wrapper(T&& e)
{
wrapped(forward<T>(e));
}
Run Code Online (Sandbox Code Playgroud)
当然,您知道何时wrapper()实例化T取决于传递给它的参数是左值还是右值。如果它是type的左值U,T则推导为U&。如果是右值,T则推导为U。
无论如何-在-的范围内wrapper()- e是一个左值,因此它始终使用的第一个重载std::forward()。
现在我的问题是:
使用(需要)第二重载的有效方案是什么?
鉴于以下参考折叠规则
T& & - > T&T&& & - > T&T& && - > T&T&& && - > T&&第三和第四条规则意味着T(ref qualifer) &&身份转变,即T&停留在T&并T&&保持在T&&.为什么我们有两个重载std::forward?以下定义无法满足所有目的吗?
template <typename T, typename = std::enable_if_t<!std::is_const<T>::value>>
T&& forward(const typename std::remove_reference<T>::type& val) {
return static_cast<T&&>(const_cast<T&&>(val));
}
Run Code Online (Sandbox Code Playgroud)
这里服务的唯一目的const std::remove_reference<T>&是不复制.并且enable_if有助于确保仅在非const值上调用该函数.我不完全确定是否const_cast需要它,因为它不是引用本身的const.
由于forward总是使用显式模板参数调用,因此我们需要考虑两种情况:
forward<Type&>(val)这里Tin 的类型forward将是T&,因此返回类型将是身份转换T&forward<Type&&>(val)这里Tin 的类型forward将是T&& …我知道第二次过载std::forward:
template< class T >
constexpr T&& forward( std::remove_reference_t<T>&& t ) noexcept;
Run Code Online (Sandbox Code Playgroud)
用于右值(如 Howard Hinnant 在他的回答中所述:How does std::forward receive the right argument?)
cppreference.com上有一个使用此重载的示例( Praetorian 的How does std::forward receive the right argument?中也提到了这一点):
- 将右值作为右值转发,并禁止将右值作为左值转发 此重载使得可以将表达式(例如函数调用)的结果(可能是右值或左值)转发为转发引用参数的原始值类别。
例如,如果包装器不仅转发其参数,还调用该参数的成员函数,并转发其结果:
Run Code Online (Sandbox Code Playgroud)// transforming wrapper template<class T> void wrapper(T&& arg) { foo(forward<decltype(forward<T>(arg).get())>(forward<T>(arg).get())); }其中 arg 的类型可以是
Run Code Online (Sandbox Code Playgroud)struct Arg { int i = 1; int get() && { return i; } // call to this overload is rvalue int& get() & { …