是否有任何用例 std::forward 带有 prvalue
Are there any use cases for std::forward with a prvalue?
std::forward
最常见的用法是完美转发(通用)引用,例如
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
这里param
是一个lvalue
,std::forward
最终将其转换为右值或左值,这取决于限定在它的参数是什么。
从 cppreference.com 看std::forward
的定义,我发现还有一个rvalue
重载
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
谁能给我任何理由为什么rvalue
超载?我看不到任何用例。如果要将 rvalue 传递给函数,可以按原样传递它,无需对其应用std::forward
。
这与std::move
不同,我明白为什么人们还想要一个rvalue
重载:你可能会处理你不知道你被传递了什么的泛型代码,你想要对移动语义的无条件支持,例如,参见为什么 std::move 采用通用引用?
编辑为了澄清这个问题,我问为什么从这里重载(2)是必要的,以及它的用例。
好的,因为@vsoftco要求简洁的用例,这里有一个改进的版本(使用他的想法,让"my_forward"实际看到重载被调用)。
我通过提供一个代码示例来解释"用例",如果没有 prvalue 就无法编译或行为不同(无论这是否真的有用)。
我们有 2 个重载用于std::forward
#include <iostream>
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type& t) noexcept
{
std::cout<<"overload 1"<<std::endl;
return static_cast<T&&>(t);
}
template <class T>
inline T&& my_forward(typename std::remove_reference<T>::type&& t) noexcept
{
std::cout<<"overload 2"<<std::endl;
static_assert(!std::is_lvalue_reference<T>::value,
"Can not forward an rvalue as an lvalue.");
return static_cast<T&&>(t);
}
我们有 4 个可能的用例
用例 1
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例 2
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &&
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
用例 3
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(v)); // &
return 0;
}
用例 4
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// &
Library( vector<int> a):b(a){
}
};
int main()
{
vector<int> v;
v.push_back(1);
Library a( my_forward<vector<int>>(std::move(v))); //&&
return 0;
}
这是一份简历
- 使用重载 1,没有它,你会得到编译错误
- 使用重载 2,没有它,你会得到编译错误
- 使用重载 1,没有它,你会得到编译错误
- 使用重载 2,没有它,你会得到编译错误
请注意,如果我们不使用转发
Library a( std::move(v));
//and
Library a( v);
你会得到:
- 编译错误
- 编译
- 编译
- 编译
如您所见,如果您只使用两个forward
重载中的一个,则基本上会导致不编译 4 个案例中的 2 个,而如果您根本不使用forward
,您将只能编译 3 个案例中的 4 个。
这个答案是为了回答@vsoftco的评论
@DarioOO感谢您的链接。你能写一个简洁的答案吗?从您的示例中,我仍然不清楚为什么还需要为 rvalues 定义 std::forward
总之:
因为如果没有右值专用化,以下代码将无法编译
#include <utility>
#include <vector>
using namespace std;
class Library
{
vector<int> b;
public:
// hi! only rvalue here :)
Library( vector<int>&& a):b(std::move(a)){
}
};
int main()
{
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v));
return 0;
}
但是我忍不住打字更多,所以这也是答案的不简洁版本。
长版本:
您需要移动v
,因为类Library
没有接受 lvalue 的构造函数,而只有一个右值引用。 如果没有完美的转发,我们最终会陷入不希望的行为:
包装函数在传递重物时会产生高性能损失。
使用移动语义,我们确保尽可能使用移动构造函数。 在上面的例子中,如果我们删除std::forward
代码将无法编译。
那么,forward
,在没有我们达成共识的情况下移动元素,实际上在做什么呢?不!
它只是创建矢量的副本并移动它。我们怎么能确定这一点呢?只需尝试访问该元素。
vector<int> v;
v.push_back(1);
A a( forward<vector<int>>(v)); //what happens here? make a copy and move
std::cout<<v[0]; // OK! std::forward just "adapted" our vector
如果您改为移动该元素
vector<int> v;
v.push_back(1);
A a( std::move(v)); //what happens here? just moved
std::cout<<v[0]; // OUCH! out of bounds exception
因此,需要重载来实现仍然安全的隐式转换,但没有重载就不可能实现。
事实上,下面的代码就是不能编译:
vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor
实际用例:
您可能会争辩说,转发参数可能会创建无用的副本,从而隐藏可能的性能影响,是的,这实际上是真的,但请考虑实际用例:
template< typename Impl, typename... SmartPointers>
static std::shared_ptr<void>
instancesFactoryFunction( priv::Context * ctx){
return std::static_pointer_cast<void>( std::make_shared<Impl>(
std::forward< typename SmartPointers::pointerType>(
SmartPointers::resolve(ctx))...
) );
}
代码取自我的框架(第 80 行):Infectorpp 2
在这种情况下,参数是从函数调用转发的。SmartPointers::resolve
的返回值被正确移动,而不管Impl
的构造函数接受 rvalue 或 lvalue(所以没有编译错误,无论如何都会移动这些错误)。
基本上,在任何情况下,您都可以使用std::foward
,希望使代码更简单,更具可读性,但您必须牢记2点
- 额外的编译时间(实际上没有那么多)
- 可能会导致不需要的副本(当您没有明确地将某些内容移动到需要右值的内容中时)
如果使用得小心,是一个强大的工具。
我之前盯着这个问题,看了霍华德·欣南特的链接,思考了一个小时后无法完全理解。现在我正在寻找并在五分钟内得到了答案。(编辑:得到答案太慷慨了,因为Hinnant的链接有答案。我的意思是我理解了,并且能够以更简单的方式解释它,希望有人会觉得有帮助)。
基本上,这允许您在某些情况下成为通用的,具体取决于传入的类型。请考虑以下代码:
#include <utility>
#include <vector>
#include <iostream>
using namespace std;
class GoodBye
{
double b;
public:
GoodBye( double&& a):b(std::move(a)){ std::cerr << "move"; }
GoodBye( const double& a):b(a){ std::cerr << "copy"; }
};
struct Hello {
double m_x;
double & get() { return m_x; }
};
int main()
{
Hello h;
GoodBye a(std::forward<double>(std::move(h).get()));
return 0;
}
此代码打印"移动"。有趣的是,如果我删除std::forward
,它会打印副本。对我来说,这很难让我思考,但让我们接受它并继续前进。(编辑:我想发生这种情况是因为 get 将返回对右值的左值引用。这样的实体衰减成左值,但 std::forward 会将其转换为右值,就像 forward 的常见用法一样。不过还是觉得不直观)。
现在,让我们想象另一个类:
struct Hello2 {
double m_x;
double & get() & { return m_x; }
double && get() && { return std::move(m_x); }
};
假设在main
的代码中,h
是Hello2的一个实例。现在,我们不再需要 std::forward,因为对std::move(h).get()
的调用会返回一个 rvalue。但是,假设代码是通用的:
template <class T>
void func(T && h) {
GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}
现在,当我们调用func
时,我们希望它与Hello
和Hello2
正常工作,即我们想要触发一个移动。如果我们包括外std::forward
,这只会发生在Hello
的右值,所以我们需要它。但。。。我们到了重点。当我们将Hello2
的右值传递给此函数时,get() 的右值重载已经返回右值双精度,因此std::forward
实际上接受右值。因此,如果没有,您将无法编写如上所述的完全通用代码。
该死的。
- 完美前进使用 std::forward vs RefRefCast
- 返回指向对象的指针的函数调用是否为 prvalue?
- 一元*运算符的操作数是否期望一个 prvalue
- 你如何理解"std: :forward is just syntactic sugar"?这是真的吗?
- "std::forward"和"std::move"真的不生成代码吗?
- 在C++中使用 std::forward 的多个参数
- 如何使用 std::forward 精确地评估参数包的扩展?
- 使用 prvalue 创建shared_pointer
- 初始化声明符是 prvalue 表达式吗?
- 普通的右值引用和 std::forward 返回的引用有什么区别?
- 怎么可能写 f( *this, std::forward<Args>(args)... ) 而 f 只用 F f 声明;
- 如何通过通用引用或std::forward将这三个c++模板函数合并为一个
- C++模块(MSVC)中的visual Forward声明
- 自 C++11 年以来对 std::forward 实施的理解
- 声明规则定义了lvalue或prvalue
- 当将参数包传递给另一个可变参数模板函数时,我是否必须使用 std::forward
- 为什么要在概念中使用std::forward
- 为什么在提到 prvalue 时,它在这里使用术语"object"?
- 寻求对std::forward的澄清
- 是否有任何用例 std::forward 带有 prvalue