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 asstd::cin
orstd::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:
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
};
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
In C++, Differencebetween a Method and a Function
Noexcept, Stack Unwinding and Performance
Sfinae and Partial Class Template Specializations
What's the Point of Std::Unique_Ptr::Get
Typecasting Eigen::Vectorxd to Std::Vector
C++ Array of Pointers: Delete or Delete []
How to Simulate Interfaces in C++
How Does Qt Delete Objects? and How to Store Qobjects
Opencv - Dll Missing, But It's Not
Search 25 000 Words Within a Text
Best Practices for Use of C++ Header Files
G++: in What Order Should Static and Dynamic Libraries Be Linked
What Is the Purpose of C++20 Std::Common_Reference
What Is Allowed in a Constexpr Function
How to Declare Array with Auto
Virtual Destructor: Is It Required When Not Dynamically Allocated Memory