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")
Related
I've just read this in C++ Primer :
A function parameter that is an rvalue reference to a template type
parameter (i.e., T&&) preserves the constness and lvalue/rvalue
property of its corresponding argument.
But I don't understand why T&& parameters have this feature while T&'s haven't.
What's the C++ logic behind this ?
Because if T is a function template argument, then T& is a non-constant reference to T while T&& is called a forwarding reference if used as type for a function argument. Other T&&, e.g. in template<typename T> void foo(std::vector<T&&> x) is really an r-value reference that cannot deduce const (not that const r-value references are very useful).
Since we want to automatically differentiate between the following cases:
const int x = 5; foo(x);
int x = 5; foo(std::move(x));
int x = 5; foo(x);
foo(int{5});
while retaining a single template<typename T> foo(T&& x) definition, forwarding references were given the ability to deduce const.
As to why T& cannot deduce const, it likely never was the intent of this feature. It really meant to serve as a non-constant reference to a generic type. It would make x.non_const_member() fail to compile sometimes.
Yes, that happens for T&& too but the intent here is exactly about forwarding the type as it was passed to us to somewhere else, not necessarily modifying the object ourselves.
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.
According to http://en.cppreference.com/w/cpp/utility/move
std::move is declared as follows:
template <typename T>
std::remove_reference<T>::type&& move(T&& t);
As far as my understanding goes, when code is templated, the deduction of T in typename T looses information about reference, so following:
template <typename T>
void someFunction(T&& value);
when used like:
int five=5;
someFunction(five);
then
value is of type int&
T is int
or
const float value = 5.25;
someFunction(value);
then
value is of type const float&
T is const float.
If this is so, then there is no point in move declaration to declare returned type as:
std::remove_reference<T>::type&&, because T is already not a reference.
Moreover, if std::move takes as argument a reference (l-value reference in practice), then returning static_cast<T&&>(t) in std::move in fact due to reference collapsing will return l-value reference or r-value reference, so it will behave more like std::forward not move. So what is a trick, that makes it work properly, that I do not understand?
Your examples are incorrect:
int five=5;
someFunction(five);
In this case, T is deduced as int&, not int. The same goes for the second example; T is deduced as const int&.
As such, returning just T&& would mean T&& &, which is T& due to reference collapsing rules.
This is why the std::remove_reference is required, to ensure that there are no references on the type to prevent reference collapsing from taking place.
is there a difference between move and forward here:
void test(int && val)
{
val=4;
}
void main()
{
int nb;
test(std::forward<int>(nb));
test(std::move(nb));
std::cin.ignore();
}
In your specific case, no, there isn't any difference.
Detailed answer:
Under the hood, std::move(t) does static_cast<typename std::remove_reference<T>::type&&>(t), where T is type of t (see §20.2.3/6). In your case, it resolves to static_cast<int&&>(nb).
forward is a little bit tricky, because it is tailored for use in templates (to allow perfect forwarding) and not as a tool to cast lvalue to rvalue reference.
Standard library provides two overloads (one for lvalue references and the second for rvalue ones, see §20.2.3/2):
template <class T> T&& forward(typename remove_reference<T>::type& t) noexcept;
template <class T> T&& forward(typename remove_reference<T>::type&& t) noexcept;
Substituting int, we get:
int&& forward(int& t) noexcept;
int&& forward(int&& t) noexcept;
And since nb is lvalue, the first version is chosen. According to standard draft, the only effect of forward is static_cast<T&&>(t). With T being int, we get static_cast<int&&>(nb), i.e. - we get two exactly same casts.
Now, if you want to cast lvalue to rvalue (to allow moving), please use only std::move, which is the idiomatic way to do this conversion. std::forward is not intended to be used this way.
No difference.
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.