可以将文章内容翻译成中文,广告屏蔽插件可能会导致该功能失效(如失效,请关闭广告屏蔽插件后再试):
问题:
The most common usage of std::forward
is to, well, perfect forward a forwarding (universal) reference, like
template<typename T>
void f(T&& param)
{
g(std::forward<T>(param)); // perfect forward to g
}
Here param
is an lvalue
, and std::forward
ends up casting it to a rvalue or lvalue, depending on what the argument that bounded to it was.
Looking at the definition of std::forward
from cppreference.com I see that there is also a rvalue
overload
template< class T >
T&& forward( typename std::remove_reference<T>::type&& t );
Can anyone give me any reason why the rvalue
overload? I cannot see any use case. If you want to pass a rvalue to a function, you can just pass it as is, no need to apply std::forward
on it.
This is different from std::move
, where I see why one wants also a rvalue
overload: you may deal with generic code in which you don't know what you're being passed and you want unconditional support for move semantics, see e.g. Why does std::move take a universal reference?.
EDIT To clarify the question, I'm asking why overload (2) from here is necessary, and a use case for it.
回答1:
Ok since @vsoftco asked for concise use case here's a refined version (using his idea of having "my_forward" to actually see wich overload gets called).
I interpret "use case" by providing a code sample that without prvalue not compile or behave differently (regardless of that would be really usefull or not).
We have 2 overloads for 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);
}
And we have 4 possible use cases
Use case 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;
}
Use case 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;
}
Use case 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;
}
Use case 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;
}
Here's a resume
- Overload 1 is used, without it you get compilation error
- Overload 2 is used, without it you get compilation error
- Overload 1 is used, wihtout it you get compilation error
- Overload 2 is used, without it you get compilation error
Note that if we do not use forward
Library a( std::move(v));
//and
Library a( v);
you get:
- Compilation error
- Compile
- Compile
- Compile
As you see, if you use only one of the two forward
overloads, you basically cause to not compile 2 out of 4 cases, while if you do not use forward
at all you would get to compile only 3 out of 4 cases.
回答2:
This answer is for answering comment by @vsoftco
@DarioOO thanks for the link. Can you maybe write a succinct answer? From your example it's still not clear for me why does std::forward need to be also defined for rvalues
In short:
Because without a rvalue specialization the following code would not compile
#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;
}
however I can't resist to type more so here's also the not succint version of the answer.
Long version:
You need to move v
because the class Library
has no constructor accepting lvalue to it, but only a rvalue reference.
Without perfect forwarding we would end up in a undesired behaviour:
wrapping functions would incurr high performance penality when passing heavy objects.
with move semantics we make sure that move constructor is used IF POSSIBLE.
In the above example if we remove std::forward
the code will not compile.
So what is actually doing forward
? moving the element without our consensus? Nope!
It is just creating a copy of the vector and moving it. How can we be sure about that? Simply try to access the element.
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
if you instead move that element
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
So that overload is needed to make possible a implicit conversion that is still safe, but not possible without the overload.
Infact the following code will just not compile:
vector<int> v;
v.push_back(1);
A a( v); //try to copy, but not find a lvalue constructor
Real use case:
You may argue that forwarding arguments may create useless copies and hence hide a possible performance hit, yes, that's actually true, but consider real use cases:
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))...
) );
}
Code was taken from my framework (line 80): Infectorpp 2
In that case arguments are forwarded from a function call. SmartPointers::resolve
's returned values are correctly moved regardless of the fact that constructor of Impl
accept rvalue or lvalue (so no compile errors and those get moved anyway).
Basically you can use std::foward
in any case in wich you want to make code simpler and more readable but you have to keep in mind 2 points
- extra compile time (not so much in reality)
- may cause unwanted copies (when you do not explicitly move something into something that require a rvalue)
If used with care is a powerfull tool.
回答3:
I stared at this question before, read Howard Hinnant's link, couldn't fully grok it after an hour of thinking. Now I was looking and got the answer in five minutes. (Edit: got the answer is too generous, as Hinnant's link had the answer. I meant that I understood, and was able to explain it in a simpler way, which hopefully someone will find helpful).
Basically, this allows you to be generic in certain kinds of situations depending on the typed that's passed in. Consider this code:
#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;
}
This code prints "move". What's interesting is that if I remove the std::forward
, it prints copy. This, for me, is hard to wrap my mind around, but let's accept it and move on. (Edit: I suppose this happens because get will return a lvalue reference to an rvalue. Such an entity decays into an lvalue, but std::forward will cast it into an rvalue, just as in the common use of forward. Still feels unintuitive though).
Now, let's imagine another class:
struct Hello2 {
double m_x;
double & get() & { return m_x; }
double && get() && { return std::move(m_x); }
};
Suppose in the code in main
, h
was an instance of Hello2. Now, we no longer need std::forward, because the call to std::move(h).get()
returns an rvalue. However, suppose the code is generic:
template <class T>
void func(T && h) {
GoodBye a(std::forward<double>(std::forward<T>(h).get()));
}
Now when we call func
, we'd like it to work properly with both Hello
and Hello2
, i.e. we'd like to trigger a move. That only happens for an rvalue of Hello
if we include the outer std::forward
, so we need it. But... We got to the punchline. When we pass an rvalue of Hello2
to this function, the rvalue overload of get() will already return an rvalue double, so std::forward
is actually accepting an rvalue. So if it didn't, you wouldn't be able to write fully generic code as above.
Damn.