Reputation: 42245
#include <vector>
using namespace std;
vector<int> f()
{
return{};
}
// Update: Below is not compilable
void g(vector<int>)
{}
// Update: Below is the my initial intent.
/*
void g(const vector<int>&)
{}
*/
void g(vector<int>&&)
{}
int main()
{
auto v1 = f();
auto&& v2 = f();
g(forward<vector<int>>(v1));
g(forward<vector<int>>(v2));
}
Does C++11 guarantee g(forward<vector<int>>(v1))
will call f(vector<int>)
or f(const vector<int>&)
and g(forward<vector<int>>(v2))
will call f(vector<int>&&)
?
Upvotes: 1
Views: 151
Reputation: 141554
There is an object associated with a return-by-value function called the return value. The return value of f()
is a vector<int>
.
In C++11 and C++14, where f()
returns by value:
auto v1 = f();
initializes a vector<int>
, which will be known as v1
, from the return value, using the copy/move constructor. This is a copy elision context. auto&& v2 = f();
makes the name v2
designate the return value, and the return value's lifetime is extended.If the compiler does implement copy-elision then these two codes have the same effect. From C++17, with the so-called "guaranteed copy elision", these two codes will be the same.
By "the same", I mean the same in all respects except for the result of decltype(identifier)
as discussed below.
There is no difference between your two g
calls. In both cases, the argument is an lvalue of type std::vector
. The expressions v1
and v2
do not "remember" whether they were originally a return value object or not.
std::forward
is only useful when given a template parameter that is the result of perfect forwarding deduction (therefore, may be a reference type).
It was suggested to use decltype
. decltype(identifier)
is a special case that does recall how the identifier was declared, after auto
deduction has been applied.
decltype(v1)
is vector<int>
decltype(v2)
is vector<int> &&
But now we have:
std::forward<decltype(v1)>
is vector<int> &&
std::forward<decltype(v2)>
is vector<int> &&
So you still do not distinguish the two different forms of g
.
In fact, as noted in comments, it is not possible to call g
at all. Every call will be ambiguous. In overload resolution, direct reference binding is an identity conversion; an xvalue argument of type T
is an equal match for parameter T
as for T&&
. (Similarly, an lvalue argument of type T
would be an equal match for parameter T
as for T&
).
It would be possible to overload g
to have lvalue vs. rvalue overloads. But you'd need to make changes to v1
's initialization too:
void g(vector<int> const &) {}
void g(vector<int> &&) {}
// ...
auto const& v1 = f();
auto&& v2 = f();
g( std::forward<decltype(v1)>(v1) );
g( std::forward<decltype(v2)>(v2) );
Upvotes: 2
Reputation: 119099
The difference is that v1
is a vector while v2
is an rvalue reference to a vector.
Overloading g
the way you have done is a very bad idea. If the argument is a cv-unqualified rvalue, then the call will be ambiguous since both g
's can accept the rvalue with the identity conversion sequence. It is, however, acceptable to have one overload take T&
and the other take T&&
.
If you want to forward the value category of f()
, don't copy/move it as you have done into v1
. That destroys the value category information. v1
will always be an lvalue.
Besides, you are not using std::forward
properly. Both v1
and v2
will be cast to rvalue reference, and would behave the same way under overload resolution in that case.
Here is what the proper usage of std::forward
looks like:
void g(vector<int>&);
void g(vector<int>&&);
int main() {
auto&& v1 = function_returning_lvalue_vector();
auto&& v2 = function_returning_rvalue_vector();
g(forward<decltype(v1)>(v1)); // calls lvalue overload
g(forward<decltype(v2)>(v2)); // calls rvalue overload
}
Upvotes: 3