Bernard
Bernard

Reputation: 5666

C++ pass parameter by rvalue reference if possible, otherwise copy the lvalue reference

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

Answers (4)

songyuanyao
songyuanyao

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

Sam Varshavchik
Sam Varshavchik

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

NathanOliver
NathanOliver

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

The Quantum Physicist
The Quantum Physicist

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

Related Questions