const applied to "universal reference" parameter - c++

I've stumbled upon Scott Mayers article on universal references, link.
From what I understood universal reference, that is some type T&& can mean an rvalue or lvalue type in different contexts.
For example:
template<typename T>
void f(T&& param); // deduced parameter type ⇒ type deduction;
// && ≡ universal reference
In the above example depending on template parameter the T&& can be either an lvalue or rvalue, that is, it depends on how we call f
int x = 10;
f(x); // T&& is lvalue (reference)
f(10); // T&& is rvalue
However according to Scott, if we apply const to to above example the type T&& is always an rvalue:
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
Quote from the article:
Even the simple addition of a const qualifier is enough to disable the
interpretation of “&&” as a universal reference:
Question:
Why does const make an "universal" reference rvalue?
I think this is impossible because following code makes confusion then:
template<typename T>
void f(const T&& param); // “&&” means rvalue reference
int x = 10;
f(x); // T&& is lvalue (reference) // how does 'x' suddenly become an rvalue because of const?
f(10); // T&& is rvalue // OK

Related

Why can't I bind an lvalue-reference to an rvalue while a concept can?

#include <utility>
template<typename T>
concept IsWritable = requires(T& obj)
{
obj = 0;
};
template<typename T>
void f(T&)
{}
int main()
{
static_assert(IsWritable<int&&>); // ok
int n = 0;
f(std::move(n)); // error
}
GCC error message:
error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type
'std::remove_reference<int&>::type' {aka 'int'}
Why can't I bind an lvalue-reference to an rvalue while a concept can?
Given
template<typename T>
void f(T&)
{}
the line
f(std::move(n)); // error
must error, because you're trying to pass an rvalue argument, std::move(n), to a lvalue reference parameter, T&.
As regards the concept, notice that there's no argument-parameter pair on the value level. And it's on the value level that talking about rvalue/lvalue-ness makes sense (after all, those are called value categories).
There's an argument-parameter pair on the type level, though: you're explicitly passing the int&& template argument to IsWritable's template argument T, so the T& will be int&& &, which is int&, because normal reference collpasing applies (it doesn't just apply to universal references).

Why a template argument of a rvalue reference type can be bound to a lvalue type?

As I know, a rvalue reference cannot be bound to a lvalue.
e.g.,
void func(Foo &&f) {}
int main() {
Foo f;
func(f);
}
compiler complains:
error: cannot bind rvalue reference of type ‘Foo&&’ to lvalue of type ‘Foo
But, why a template argument of rvalue reference type can be bound to a lvalue?
e.g.,
template <typename T> void funcTemp(T &&arg) {}
int main() {
Foo f;
funcTemp(f);
}
The compiler won't complain the error.
Why?
You can read this article Universal References in C++11 to understand. Here some part of it:
If a variable or parameter is declared to have type T&& for some deduced type T, that variable or parameter is a universal reference.
Widget&& var1 = someWidget; // here, “&&” means rvalue reference
auto&& var2 = var1; // here, “&&” does not mean rvalue reference
template<typename T>
void f(std::vector<T>&& param); // here, “&&” means rvalue reference
template<typename T>
void f(T&& param); // here, “&&”does not mean rvalue reference
Here a relevant for your case excerpt from the Standard:
... function template parameter type (call it P) ... 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.

std::is_pointer check with universal references

I have a function I've written to check if a given variable is of pointer type:
template<typename T>
void CheckIfPointer(T&& value)
{
static_assert(std::is_pointer<typename std::remove_reference<T>::type>::value, "T must be of pointer type");
}
I'm using a universal reference here because I do not want to restrict the value categories that could be passed in. I noticed, however, that in this example:
char const* mystr = "Hello World";
CheckIfPointer(mystr);
Type T is actually const char *& (according to clang). So is the remove_reference the appropriate solution here? Or is there a cleaner way of checking the actual type, without references getting in the way?
Note that I'm only supporting up to C++14.
Type T is actually const char *& (according to clang).
There is a special rule in template argument deduction that was introduced to permit perfect-forwarding. In the context of template argument deduction, T&& is not an rvalue reference but a forwarding reference instead.
If an lvalue is passed to a function template taking a forwarding reference, the type parameter is deduced as T& instead of T. This allows reference collapsing to take place: T& && becomes T&.
From cppreference:
If P is an rvalue reference to a cv-unqualified template parameter (so-called forwarding reference), and the corresponding function call argument is an lvalue, the type lvalue reference to A is used in place of A for deduction (Note: this is the basis for the action of std::forward Note: in class template argument deduction, template parameter of a class template is never a forwarding reference (since C++17))
template<class T>
int f(T&&); // P is an rvalue reference to cv-unqualified T (forwarding reference)
template<class T>
int g(const T&&); // P is an rvalue reference to cv-qualified T (not special)
int main()
{
int i;
int n1 = f(i); // argument is lvalue: calls f<int&>(int&) (special case)
int n2 = f(0); // argument is not lvalue: calls f<int>(int&&)
// int n3 = g(i); // error: deduces to g<int>(const int&&), which
// cannot bind an rvalue reference to an lvalue
}
So is the remove_reference the appropriate solution here? Or is there a cleaner way of checking the actual type, without references getting in the way?
Yes, remove_reference is appropriate here. You might want to use std::remove_reference_t to avoid the explicit typename and ::type.
Also, why are you passing pointers by forwarding reference? Are you sure you don't want to pass by value or by lvalue reference?
Consider taking a const T& instead:
template<typename T>
void CheckIfPointer(const T& value)
{
static_assert(std::is_pointer<T>::value, "T must be of pointer type");
}

Is this a forwarding reference?

The distinction between rvalue references and forwarding references was made clear enough in this example by Scott Meyers:
Widget&& var1 = someWidget; // here, “&&” means rvalue reference (1)
auto&& var2 = var1; // here, “&&” does not mean rvalue reference (2)
template<typename T>
void f(std::vector<T>&& param); // here, “&&” means rvalue reference (3)
template<typename T>
void f(T&& param); // here, “&&”does not mean rvalue reference (4)
Essentially the distinction happens when we have a deducible context, hence case (3) explicitly states that we have a vector<...>&& whereas the T in case (4) is to be deduced and (after applying reference collapsing rules) categorized in terms of "value category".
But what happens with a bit more complex pattern matching? Take the following case for example :
template <template <class...> class Tuple, class... Ts>
void f(Tuple<Ts...>&& arg)
{
}
What does && mean here ?
In your last example, arg is an rvalue reference.
A forwarding reference is an rvalue reference to a cv-unqualified template parameter
and Tuple<Ts...> is not a template parameter.
(Citation from [temp.deduct.call].)
It is a rvalue reference, not a forwarding reference.
The easiest way to be sure is to try to pass an lvalue, if it fails, then it is a rvalue reference, if not, then a forwarding reference:
template<typename... Ts>
struct foo {};
//f function definition
int main() {
foo<int, double> bar;
f(bar); // fails! Cannot bind lvalue to rvalue reference
f(foo<int, double>{}); // ok, rvalue is passed
}
The concept forwarding reference is not a standard concept, it is usefull de recognize it when you see it, but if you want to understand and deal with it correctly you must understand reference arithmetics. (I believe Meyer's book has also a chapter about it)
What is behind the concept of a forwarding reference is the reference arithmetic:
&& && = &&
&& & = &
& && = &
& & = &
Let's simulate compiler template type deduction with a forwarding reference
template<class T>
void foo(T&&);
//...
const int i=42;
foo(i); // the compiler will defines T = const int &
// T&& = const int & && = const int &
// => the compiler instantiates void foo<const int &>(const int &);
foo(6*7);// the compiler will defines T = int
// T&& = int &&
// the compiler instantiates void foo<int>(int &&);
in such a situation, the instantiation of the template foo can produces
a function wich takes argument by lvalue reference or functions which takes arguments rvalue reference: a forwarding reference is either
a rvalue reference or a lvalue reference, depending on template type deduction. It is named like this, because in such a situation, the parameter shall be passed either as an lvalue or as an xvalue, and this is the job of T&& std::forward<T>(T&& a)
If you declare a function has:
template<class T>
void foo(ATemplateClass<T> && a);
whatever the type deduced for T by the compiler, you'll get a rvalue reference paramater.

When you perfect-forward, typename T becomes a T& or T&&, but when you don't, T isn't a reference at all. How?

I'm reading about perfect forwarding, and this is something I've learnt that has confused me: When you're trying to achieve perfect forwarding, you'll do something like this:
template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T &&x); // like this: void foo(int& &&x)
So then I thought, wait, does that mean that if I did this:
template<class T> // If I were to call foo with an l-value int, foo would look
void foo(T x); // like this: void foo(int& x);
But that's not what happens. foo instead looks like this: void foo(int x);
My question: How come in the perfect forwarding function, T turns into a T& or T&&, but in the other one, T isn't a reference? Can somebody tell me the exact rules for this? I need some clarification!
A template parameter T can be deduced as a reference type only if it appears in a function parameter of the form T&&
A function template of the form:
template<class T> void f(T x) will deduce T as an object type (and x is an object type so is passed by value)
template<class T> void f(T& x) will deduce T as an object type (and then x has lvalue reference type)
template<class T> void f(T&& x) will deduce T as
either an lvalue reference (so x has lvalue reference type due to reference collapsing rules)
or as an object type (so x has rvalue reference type)
How come in the perfect forwarding function, T turns into a T& or T&&, [...]
This is wrong. T becomes a reference type L& or an object type R, not a reference R&&.
The function parameter of the form T&& thus becomes
either L& (because adding an rvalue reference to an lvalue reference is still an lvalue reference, just like add_rvalue_reference<L&>::type is still L&)
or it becomes R&& (because add_rvalue_reference<R>::type is R&&)
This is because of the way type deduction is defined, and it is only related to perfect forwarding in the sense that the result of std::forward<>() is an rvalue if an rvalue reference is passed, and an lvalue if an lvalue reference is passed.
But in general, when you do not have a reference to begin with, your T is not going to be deduced as a reference type (i.e. as A&, whatever A could be). If that was the case, as Yakk correctly points out in the comments, it would be impossible to write a function template that takes its arguments by value.
In particular, the reference collapsing rule you are referring to is defined in Paragraph 14.8.2.1/4 of the C++11 Standard:
If P is a
reference type, the type referred to by P is used for type deduction. If P is an rvalue reference to a cv-unqualified
template parameter and the argument is an lvalue, the type “lvalue reference to A” is used in
place of A for type deduction. [ Example:
template <class T> int f(T&&);
template <class T> int g(const T&&);
int i;
int n1 = f(i); // calls f<int&>(int&)
int n2 = f(0); // calls f<int>(int&&)
int n3 = g(i); // error: would call g<int>(const int&&), which
// would bind an rvalue reference to an lvalue
—end example ]
There are three general cases to consider when deducing template parameters like this.
void foo(T x): this means "pass-by-value". It always deduces a type as appropriate to pass by value.
void foo(T& x): this means "pass-by-lvalue-reference". It always deduces a type as appropriate to pass by lvalue reference.
void foo(T&& x): this means "pass-by-reference". It always deduces a type as appropriate to pass by reference, which may be an lvalue reference or an rvalue reference.
Relax, slow down, and breathe.
Template argument deduction is the central mechanism that you need to understand, and it's not totally trivial. When you say template <typename T> void foo(T), then T is always deduced as a non-reference type.
If you want a reference, you have to put a & on it: template <typename T> void foo(T&) will also deduce T as a non-reference-type, but foo now always expects an lvalue reference.
The final piece of magic comes from the new reference collapsing rules. When you say template <typename T> void foo(T&&), then two things can happen:
You call foo with an rvalue, e.g. foo(Bar()). Then T is deduced as Bar, and foo takes an rvalue reference to Bar, i.e. a Bar&&.
You call foo with an lvalue, e.g. Bar x; foo(x);. Now the only thing foo can take is an lvalue reference. This requires T to be deduced as Bar&, since T&& == Bar& && == Bar&, due to the collapsing rules.
Only this final template is able to accept both lvalues and rvalues. This is why it is sometimes called "universal reference"; but remember that it's not the reference that matters, but the template argument deduction. Using std::forward<T> allows you to pass on the argument with the same value category that you received.