xmllmx
xmllmx

Reputation: 42245

What's the difference between "auto v = f()" and "auto&& v = f()"?

#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

Answers (2)

M.M
M.M

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

Brian Bi
Brian Bi

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

Related Questions