Reputation: 5666
With rvalue references, many redundant copies may be elided, but that seems to require me to write the same function multiple times (one for an rvalue reference, one for a const
lvalue reference). But the standard library seems to only need to declare some functions once.
For example:
#include <iostream>
#include <tuple>
void foo(int&& x){
x = 2;
}
int main()
{
int x = 1;
foo(x); // compile error
std::make_tuple(x); // ok
std::cout << x << std::endl;
}
Calling foo(x)
is a compile error, because I cannot convert implicitly from int
to int&&
. But I am perplexed as to why std::make_tuple
would work. The reference says that it only accepts rvalue reference parameters. It also seems to not make copies when the value passed into it is an ravlue reference, but it would make a copy (as most would expect) when used as in my sample above.
How can I make foo
work like this?
Upvotes: 6
Views: 5594
Reputation: 172864
The reference says that it only accepts rvalue reference parameters.
No, this is forwarding reference, which could serve as both lvalue reference and rvalue reference, according to the value category of the passed-in argument.
How can I make
foo
work like this?
The point of declaring a forwarding reference is (1) type deduction is necessary, that means you need to make foo
a function template here; (2) the parameter x
has the exact form of T&&
for the template parameter T
. e.g.
template <typename T>
void foo(T&& x){
x = 2;
}
then
int x = 1;
foo(x); // lvalue passed, T is deduced as int&, parameter's type is int&
foo(1); // rvalue passed, T is deduced as int, parameter's type is int&&
Note this is true for std::make_tuple
too, even it's using template parameter pack. And better to bear in mind that even forwarding reference looks like rvalue reference but they're different things.
BTW: std::forward is usually used with forwarding reference to preserve the value category of the argument e.g. when forwarding it to other functions.
Upvotes: 8
Reputation: 118292
The difference is a result of a rather subtle overload of the &&
operator when it is used with a template parameter:
template<typename foo>
void bar(foo &&baz)
This is not an rvalue reference. It is a forwarding reference. When the template gets resolved, baz
is going to be either an lvalue or an rvalue reference, depending on the call situation.
This is why you will see the C++ library provide what appears to be a single template, that works in both lvalue and rvalue contexts.
But forwarding references occur in templates only. In a non-template declaration:
void bar(int &&baz)
This is always an rvalue reference, and can only be used in rvalue contexts.
Upvotes: 4
Reputation: 180415
std::make_tuple
works because the function doesn't actually take a rvalue reference like foo
does. std::make_tuple
takes a object in the form of T&&
and since T
is a template type it makes it a forwarding reference, not a rvalue reference. A forwarding reference can bind to lvalues and rvalues where as a rvalue reference can only take a rvalue. To make foo
the same you would need
template<typename T>
void foo(T&& bar)
{
bar = 2;
}
Upvotes: 3
Reputation: 26256
Would this work for you?
#include <iostream>
#include <tuple>
void foo(int&& x){
std::cout<<"rvalue"<<std::endl;
x = 2;
}
void foo(int& x){
std::cout<<"lvalue"<<std::endl;
x = 2;
}
int main()
{
int x = 1;
foo(x); // no compile error anymore
foo(std::move(x)); // now r-value is being used
std::make_tuple(x); // ok
std::cout << x << std::endl;
}
Upvotes: 2