C++ Forwarding Reference and R-Value Reference

C++ forwarding reference and r-value reference

It's a great question which foxes almost everyone in the beginning.

template <class T>
class A
{
template <class U>
void foo(T&& t, U&& u);
};

In this example, T is not deduced (you explicitly define it when you instanciate the template).

U is deduced because it's deduced from the argument u.

Therefore, in almost all cases it would be:

std::move(t);
std::forward<U>(u);

rvalue reference or forwarding reference?

It's an rvalue reference. Forwarding references can only appear in a deduced context. This is just a member function that accepts an rvalue reference to the class template parameter.

You can't force a forwarding reference to be an rvalue reference if you want to maintain template argument deduction for functions. If you don't mind specifying the template argument all over the place, then this will always and only ever give an rvalue reference:

template<typename T> struct identity { using type = T; };

template<typename T> void func(typename identity<T>::type&&);

In retrospect, there actually is a way to maintain deduction but force only rvalue refs to be accepted (besides the self documenting one in Simple's answer). You can provide a deleted lvalue overload:

template<typename T>
void func(T&) = delete;

template<typename T>
void func(T&& s)
{
// ...
}

The lvalue overload is more specialized when passed an lvalue. And on account of being deleted, will give a somewhat clear error message.

Aren't forwarding references deduced as r-value references?

Types and value categories are two independent properties of expression.

Each C++ expression (an operator with its operands, a literal, a variable name, etc.) is characterized by two independent properties: a type and a value category. Each expression has some non-reference type, and each expression belongs to exactly one of the three primary value categories: prvalue, xvalue, and lvalue.

The type of x is int&&, but x is the name of variable and x is an lvalue expression itself, which just can't be bound to int&& (but could be bound to const int&).

(emphasis mine)

The following expressions are lvalue expressions:

  • the name of a variable, a function, a template parameter object (since C++20), or a data member, regardless of type, such as std::cin or std::endl. Even if the variable's type is rvalue reference, the expression consisting of its name is an lvalue expression;

That means when function has both lvalue-reference and rvalue-reference overloads, value categories are considered in overload resolution too.

More importantly, when a function has both rvalue reference and lvalue reference overloads, the rvalue reference overload binds to rvalues (including both prvalues and xvalues), while the lvalue reference overload binds to lvalues:

If any parameter has reference type, reference binding is accounted for at this step: if an rvalue argument corresponds to non-const lvalue reference parameter or an lvalue argument corresponds to rvalue reference parameter, the function is not viable.

std::forward is used for the conversion to rvalue or lvalue, consistent with the original value category of forwarding reference argument. When lvalue int is passed to foo, T is deduced as int&, then std::forward<T>(x) will be an lvalue expression; when rvalue int is passed to foo, T is deduced as int, then std::forward<T>(x) will be an rvalue expression. So std::forward could be used to reserve the value category of the original forwarding reference argument. As a contrast std::move always converts parameter to rvalue expression.

Have rvalue reference instead of forwarding reference with variadic template

The way to have a bunch of rvalue references is with SFINAE:

template <class... Args,
std::enable_if_t<(!std::is_lvalue_reference<Args>::value && ...), int> = 0>
void foo(Args&&... args) { ... }

Fold-expressions are C++17, it is easy enough to write a metafunction to get that same behavior in C++14. This is your only option really - you want a constrained function template deducing to rvalue references, but the only available syntax is overloaded to mean forwarding references. We could make the template parameters non-deduced, but then you'd have to provide them, which seems like not a solution at all.

With concepts, this is of course cleaner, but we're not really changing the underlying mechanism:

template <class... Args>
requires (!std::is_lvalue_reference<Args>::value && ...)
void foo(Args&&... args) { ... }

or better:

template <class T>
concept NonReference = !std::is_lvalue_reference<T>::value;

template <NonReference... Args>
void foo(Args&&... ) { ... }

It's worth pointing out that neither of these work:

template <class... Args> auto foo(const Args&... args) = delete;
template <class... Args> auto foo(Args&... args) = delete;

because they only delete overloads that take all lvalue references, and you want to delete overloads that take any lvalue references.

How to use forwarding to cast rvalue to lvalue reference?

You don't need std::forward at all here. Except for int&& i in the second overload, your parameters all declared as non-const lvalue references, so you can't pass an rvalue to any of them. And in the int&& overload, if you want to call the lvalue function from the rvalue function, you can just name the parameter i, because a name is always an lvalue.

template <typename T>
bool func(T& data, int& i) {
i = 1;
cout << "it worked!" << endl;
}

template <typename T>
bool func(T& data, int&& i) {
return func(data, i);
}

template <typename T>
bool func(T& data) {
return func(data, 0);
}

If you'd like to remove a function, note that it's actually the int& that does something effectively different: it changes i to 1. The rvalue overload technically also does, but something that has been passed as an rvalue should generally be ignored after that point, so callers should only count on func(T&, int&&) to print the message to cout. And to take an int that isn't an lvalue... just take an int.

template <typename T>
bool func(T& data, int& i) {
i = 1;
cout << "it worked!" << endl;
}

template <typename T>
bool func(T& data, int i=0) {
return func(data, i); // effect on i is ignored.
}

// Okay to pass a const int?
template <typename T>
bool func(T&, const volatile int&&) = delete;

That third deleted template preserves one behavior of your original code, though it's not clear if you actually want that behavior or not. In the function

void test() {
DataClass d;
const int n = 5;
func(d, n);
}

... the original code would have failed to compile, since a const int lvalue can't bind to either int& or int&&. But the change to a parameter of simply int would allow this test to compile, by making a copy of n to be the plain int parameter. Then the change to that int i just gets discarded, even though you gave n to the function. The deleted template is a better match for the const int lvalue n, so it would cause test to fail to compile. If you do want the behavior where func(d, n) is valid but has no effect on n, just take out that deleted template.

Appropriate way to forward rvalue reference

Citing from Effective Modern C++

From a purely technical perspective, the answer is yes: std::forward can do it all. std::move isn’t necessary. Of course, neither function is really necessary, because we could write casts everywhere, but I hope we agree that that would be,well, yucky. std::move’s attractions are convenience, reduced likelihood of error, and greater clarity.

Using std::move here

void foo(std::string&& str)
{
bar(str);
}

will return str as an rvalue reference (which is exactly what you're trying to achieve) while using std::forward would return either an lvalue reference (which you're not interested in) or an rvalue reference (thus equivalent in this case to std::move). Obviously using none would just keep calling the const std::string& str one since str is an lvalue in that function.

Bottom-line: they would do the same thing but using std::move is preferred since

  • It avoids explicitly specifying template arguments
  • It is more idiomatic
  • It goes straight to the point: std::forward is not intended to be used that way (cfr. Universal references) or in that context although it would surely work

I might agree that "I'm forwarding this rvalue reference to the other function" might make sense as a standalone sentence but it kind of misses the point of the matter. You could re-wire your brain to think it like "Keep 'moving' this rvalue reference to the other function"

Also possibly related: https://stackoverflow.com/a/18214825/1938163

why do ranges algorithms take rvalue reference as argument

Since R is a template parameter, R&& is not an rvalue reference, it is a forwarding/universal reference.

Forwarding references

Forwarding references are a special kind of references that preserve
the value category of a function argument, making it possible to
forward it by means of std::forward. Forwarding references are either:

  1. function parameter of a function template declared as rvalue
    reference to cv-unqualified type template parameter of that same
    function template:

    template<class T>
    int f(T&& x) { // x is a forwarding reference
    return g(std::forward<T>(x)); // and so can be forwarded
    }

    int main() {
    int i;
    f(i); // argument is lvalue, calls f<int&>(int&), std::forward<int&>(x) is lvalue
    f(0); // argument is rvalue, calls f<int>(int&&), std::forward<int>(x) is rvalue
    }

    template<class T>
    int g(const T&& x); // x is not a forwarding reference: const T is not cv-unqualified

    template<class T> struct A {
    template<class U>
    A(T&& x, U&& y, int* p); // x is not a forwarding reference: T is not a
    // type template parameter of the constructor,
    // but y is a forwarding reference
    };
  2. auto&& except when deduced from a brace-enclosed initializer list:

    auto&& vec = foo();       // foo() may be lvalue or rvalue, vec is a forwarding reference
    auto i = std::begin(vec); // works either way
    (*i)++; // works either way
    g(std::forward<decltype(vec)>(vec)); // forwards, preserving value category

    for (auto&& x: f()) {
    // x is a forwarding reference; this is the safest way to use range for loops
    }

    auto&& z = {1, 2, 3}; // *not* a forwarding reference (special case for initializer lists)

Is there a difference between universal references and forwarding references?

Do they mean the same thing?

Universal reference was a term Scott Meyers coined to describe the concept of taking an rvalue reference to a cv-unqualified template parameter, which can then be deduced as either a value or an lvalue reference.

At the time the C++ standard didn't have a special term for this, which was an oversight in C++11 and makes it hard to teach. This oversight was remedied by N4164, which added the following definition to [temp.deduct]:

A forwarding reference is an rvalue reference to a cv-unqualified template parameter. If P is a forwarding reference and the argument is an lvalue, the type “lvalue reference to A” is used in place of A for type deduction.

Hence, the two mean the same thing, and the current C++ standard term is forwarding reference. The paper itself articulates why "forwarding reference" is a better term than "universal reference."

Is it only a forwarding reference if the function body calls std::forward?

Nope, what you do with a forwarding reference is irrelevant to the name. The concept forwarding reference simply refers to how the type T is deduced in:

template <class T> void foo(T&& ); // <== 

It does not need to be subsequently forwarded .



Related Topics



Leave a reply



Submit