Rvalue reference parameters and template functions - c++

If I define a function which accepts an rvalue reference parameter:
template <typename T>
void fooT(T &&x) {}
I can call it, using GCC 4.5, with either a, ar, or arr:
int a, &ar = a, &&arr = 7;
fooT(a); fooT(ar); fooT(arr);
However, calling a similar, non-template function,
void fooInt(int &&x) {}
with any of those three arguments will fail. I was preparing to strengthen my knowledge of forward, but this has knocked me off course. Perhaps it's GCC 4.5; I was surprised to find that the first example from A Brief Introduction to Rvalue References also gives a compile error:
A a;
A&& a_ref2 = a; // an rvalue reference

The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.
Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.
Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.

In addition to GMan's correct answer A Brief Introduction to Rvalue References has an incorrect example because it was written prior to a language change which outlawed:
A a;
A&& a_ref2 = a; // an rvalue reference (DISALLOWED in C++11)
Despite this change in the language, the main uses cases described in the article (move and forward) are still explained correctly in the article.
Update: Oh, and the same article was originally published here with (imho) slightly better formatting.

Related

Are forwarding references always deduced as some kind of reference? [duplicate]

This question already has an answer here:
Why adding `const` makes the universal reference as rvalue
(1 answer)
Closed 7 months ago.
I think about forwarding references as part of perfect forwarding. I was trying to explain it to someone and realized I didn't know: Can it ever deduce T&& as some non-reference type?
#include <type_traits>
#include <utility>
template <typename Expected>
void checkDeduction(auto&& x) {
static_assert(std::is_same_v<Expected, decltype(x)>);
}
int main() {
checkDeduction<int&&>(1);
const volatile int x = 0;
checkDeduction<const volatile int&>(x);
checkDeduction<const volatile int&&>(std::move(x)); // Weird, but whatever.
// Is there anything for which checkDeduction<int>(foo) will compile?
}
https://godbolt.org/z/cr8K1dsKa
I think the answer is "no", that it's always either an rvalue or lvalue reference.
I think this is because if f forwards to g:
decltype(auto) f(auto&& x) { return g(std::forward<decltype(x)>(x)); }
it's not that f has the same signature as g, but rather that f is compiled with the value categories that the caller of f provides, so if g takes int, and you call f(1) then f gets an int&& and forwards that to g. At that call to g, the int&& decays to an int.
Plainly T&& is some kind of reference; the interesting question is whether T will always be deduced as a reference type. The answer is no: if the argument is an lvalue of type U, T will be U&, but if it’s an rvalue, T will just be U.
There is a sort of missed opportunity here: the language could say that a prvalue is deduced as U and an xvalue is deduced as U&& (which would still collapse with the “outer” reference operator). However, the judgment was that that distinction didn’t matter, at least not enough to justify creating additional template specializations everywhere.
It’s still possible to write f<X&&>(…) to force a forwarding reference’s template parameter to be an rvalue reference, although this wouldn’t usually accomplish anything.

Passing an lvalue to a function accepting a rvalue reference should fail, but doesn't [duplicate]

If I define a function which accepts an rvalue reference parameter:
template <typename T>
void fooT(T &&x) {}
I can call it, using GCC 4.5, with either a, ar, or arr:
int a, &ar = a, &&arr = 7;
fooT(a); fooT(ar); fooT(arr);
However, calling a similar, non-template function,
void fooInt(int &&x) {}
with any of those three arguments will fail. I was preparing to strengthen my knowledge of forward, but this has knocked me off course. Perhaps it's GCC 4.5; I was surprised to find that the first example from A Brief Introduction to Rvalue References also gives a compile error:
A a;
A&& a_ref2 = a; // an rvalue reference
The behavior of deduction in template parameters is unique, and is the reason your template version works. I've explained exactly how this deduction works here, in the context of another question.
Summarized: when the argument is an lvalue, T is deduced to T&, and T& && collapses to T&. And with the parameter at T&, it is perfectly valid to supply an lvalue T to it. Otherwise, T remains T, and the parameter is T&&, which accepts rvalues arguments.
Contrarily, int&& is always int&& (no template deduction rules to coerce it to something else), and can only bind to rvalues.
In addition to GMan's correct answer A Brief Introduction to Rvalue References has an incorrect example because it was written prior to a language change which outlawed:
A a;
A&& a_ref2 = a; // an rvalue reference (DISALLOWED in C++11)
Despite this change in the language, the main uses cases described in the article (move and forward) are still explained correctly in the article.
Update: Oh, and the same article was originally published here with (imho) slightly better formatting.

Why is T const&& not a forwarding reference?

In the context of a template, the following "reference collapsing" rules are applied:
template <typename T>
void foo(T && t)
{
//T& & -> T&
//T& && -> T&
//T&& & -> T&
//T&& && -> T&&
}
Why does the language prohibit "universal references" from having const qualifiers?
template <typename T>
void foo(T const && t)
It would seem to make sense if the type had resolved to a reference (3 out of the 4 cases).
I'm sure this idea is incompatible with some other design aspect of the language, but I can't quite see the full picture.
Originally the rvalue reference proposal said that the transformation happens if P is "an rvalue reference type". However, a defect report later noticed
Additionally, consider this case:
template <class T> void f(const T&&);
...
int i;
f(i);
If we deduce T as int& in this case then f(i) calls f<int&>(int&), which seems counterintuitive. We prefer that f<int>(const int&&) be called. Therefore, we would like the wording clarified that the A& deduction rule in 14.8.2.1 [temp.deduct.call] paragraph 3 applies only to the form T&& and not to cv T&& as the note currently implies.
There appears to have been a time period where const T &&, with T being U&, was transformed to const U&. That was changed to be consistent with another rule that says that const T, where T is U& would stay U& (cv-qualifiers on references are ignored). So, when you would deduce T in above example to int&, the function parameter would stay int&, not const int&.
In the defect report, the reporter states "We prefer that f<int>(const int&&) be called", however provides no reason in the defect report. I can imagine that the reason was that it seemed too intricate to fix this without introducing inconsistency with other rules, however.
We should also keep in mind that the defect report was made at a time where rvalue references could still bind to lvalues - i.e const int&& could bind to an int lvalue. This was prohibited only later on, when a paper by Dave & Doug, "A Safety Problem with RValue References", appeared. So, it seems to me that a deduction that works (at that time) was worth more than a deduction that simply was counter intuitive and dropped qualifiers.
This does already happen for references; if your T is a U const &, then T && will collapse to U const &. The term "universal reference" really does mean universal reference: you don't need to specify const in there to get a constant reference.
If you want to have a truly universal reference mechanism, you need your T && to be able to become all kinds of references, will all kinds of constness. And, T && does exactly that. It collapses to all four cases: both l- and r-value references, and both const and non-const.
Explained another way, the constness is an attribute of the type, not the reference, i.e. when you say T const &, you are actually talking about a U &, where U is T const. The same is true for && (although an r-value reference to a const is less useful).
This means that if you want your universal reference to collapse to a U const &, just pass it something that is of the type you want: a U const &, and it will collapse to exactly that.
To answer you question more directly: the language does not "prohibit" the use of const in the declaration of a universal reference, per sé. It is saying that if you change the mechanism for declaring a universal reference even a little bit - even by inserting a lowly const between the T and the && - then you won't have a (literally) "universal" reference anymore, because it just won't accept anything and everything.
Why do you think the language does not allow const r-value references?
In the following code, what will be printed?
#include <iostream>
struct Foo
{
void bar() const &
{
std::cout << "&\n";
}
void bar() const &&
{
std::cout << "&&\n";
}
};
const Foo make() {
return Foo{};
}
int main()
{
make().bar();
}
answer:
&&
why? Because make() returns a const object and in this context it's a temporary. Therefore r-value reference to const.
Template argument deduction has a special case for "rvalue reference to cv-unqualified template parameters". It is this very special case that forwarding/universal references rely on. See section "Deduction from a function call" in the linked article for details.
Note that before template argument deduction, all top-level cv-qualifiers are removed; however, references never have top-level cv-qualifiers and above rule does not apply, so the special rule also does not apply. (In contrast to pointers, there is no "const reference", only "reference to const")

What is Perfect Forwarding equal to

I know that this can be used to perform perfect forwarding:
template <typename A>
void foo(A&&) { /* */ }
This can be used to perform perfect forwarding on a certain type:
template <typename A, std::enable_if_t<std::is_same<std::decay_t<A>, int>::value, int> = 0>
void foo(A&&) { /* */ }
But these are just templates for functions, which means, that these get expanded to some functions, which are then used for every special case in which it might be used. However do these get expanded to:
void foo(A&) and void foo(A&&)
OR
void foo(A&) and void foo(A)
I always thought, it would be the first one, but then I noticed, that in that case, you wouldn't be able to use A const as an argument to the function, which certainly works.
However the second would be ambiguous, if you used a normal non-const lvalue. Does it call foo(A&) or foo(A)?
It's the first one. The second wouldn't make very much sense: there is no A such that A&& is a non-reference type.
If the argument is an lvalue of type cv T, then A is deduced as cv T&. If the argument is an rvalue of type cv T, then A is deduced as cv T and A&& is cv T&&. So when you pass in a const lvalue, the specialization generated is one that can accept a const argument.
They were called originally "Univeral References" by Scott Meyers, and now "Forwarding References".
As you can see, the references part has not changed. You pass in any kind of rvalue, you get a rvalue reference. You pass in any kind of lvalue, and you get a lvalue reference. Life is that simple.

Usage of std::forward vs std::move

I always read that std::forward is only for use with template parameters. However, I was asking myself why. See the following example:
void ImageView::setImage(const Image& image){
_image = image;
}
void ImageView::setImage(Image&& image){
_image = std::move(image);
}
Those are two functions which basically do the same; one takes an l-value reference, the other an r-value reference. Now, I thought since std::forward is supposed to return an l-value reference if the argument is an l-value reference and an r-value reference if the argument is one, this code could be simplified to something like this:
void ImageView::setImage(Image&& image){
_image = std::forward(image);
}
Which is kind of similar to the example cplusplus.com mentions for std::forward (just without any template parameters). I'd just like to know, if this is correct or not, and if not why.
I was also asking myself what exactly would be the difference to
void ImageView::setImage(Image& image){
_image = std::forward(image);
}
You cannot use std::forward without explicitly specifying its template argument. It is intentionally used in a non-deduced context.
To understand this, you need to really understand how forwarding references (T&& for a deduced T) work internally, and not wave them away as "it's magic." So let's look at that.
template <class T>
void foo(T &&t)
{
bar(std::forward<T>(t));
}
Let's say we call foo like this:
foo(42);
42 is an rvalue of type int.
T is deduced to int.
The call to bar therefore uses int as the template argument for std::forward.
The return type of std::forward<U> is U && (in this case, that's int &&) so t is forwarded as an rvalue.
Now, let's call foo like this:
int i = 42;
foo(i);
i is an lvalue of type int.
Because of the special rule for perfect forwarding, when an lvalue of type V is used to deduce T in a parameter of type T &&, V & is used for deduction. Therefore, in our case, T is deduced to be int &.
Therefore, we specify int & as the template argument to std::forward. Its return type will therefore be "int & &&", which collapses to int &. That's an lvalue, so i is forwarded as an lvalue.
Summary
Why this works with templates is when you do std::forward<T>, T is sometimes a reference (when the original is an lvalue) and sometimes not (when the original is an rvalue). std::forward will therefore cast to an lvalue or rvalue reference as appropriate.
You cannot make this work in the non-template version precisely because you'll have only one type available. Not to mention the fact that setImage(Image&& image) would not accept lvalues at all—an lvalue cannot bind to rvalue references.
I recommend reading "Effective Modern C ++" by Scott Meyers, specifically:
Item 23: Understand std::move and std::forward.
Item 24: Distinguish universal references for rvalue references.
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.
rvalue-reference
This function accepts rvalues and cannot accept lvalues.
void ImageView::setImage(Image&& image){
_image = std::forward(image); // error
_image = std::move(image); // conventional
_image = std::forward<Image>(image); // unconventional
}
Note first that std::move requires only a function argument, while std::forward requires both a function argument and a template type argument.
Universal references (forwarding references)
This function accepts all and does perfect forwarding.
template <typename T> void ImageView::setImage(T&& image){
_image = std::forward<T>(image);
}
You have to specify the template type in std::forward.
In this context Image&& image is always an r-value reference and std::forward<Image> will always move so you might as well use std::move.
Your function accepting an r-value reference cannot accept l-values so it is not equivalent to the first two functions.