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 .
Constant forwarding reference vs constant reference for forwarding references
What is the difference between const auto&& and const auto& in regard to universal references?
Their difference is that const auto&&
is an rvalue reference to const and const auto&
is an lvalue reference to const. What they have in common is that neither is a forwarding reference. Only auto&&
is a forwarding reference (or T&&
where T
is deduced).
const auto&&
does not bind to lvalues, unlike const auto&
and T&&
.
Why the difference in the flow of universal reference and rvalue reference
In case 1, T
is deduced to be const char (&)[12]
, not std::string
. There is no reason for the compiler to promote the string literal to std::string
yet. In case 2, every overload requires takes a reference to an std::string
, which forces the creation of a temporary std::string
to which a reference can be bound using the implicit const char*
constructor.
Note that while an rvalue reference such as std::string &&
may only bind to an rvalue, the templated equivalent T &&
may bind to both rvalues and lvalues.
Is overloading on universal references now much safer with concepts in c++ 20
I would say no. I mean, concepts help, because the syntax is nicer than what we had before, but it's still the same problem.
Here's a real-life example: std::any
is constructible from any type that is copy constructible. So there you might start with:
struct any {
template <class T>
requires std::copy_constructible<std::decay_t<T>>
any(T&&);
any(any const&);
};
The problem is, when you do something like this:
any a = 42; // calls any(T&&), with T=int
any b = a; // calls any(T&&), with T=any
Because any
itself is, of course, copy constructible. This makes the constructor template viable, and a better match since it's a less-const-qualified reference.
So in order to avoid that (because we want b
to hold an int
, and not hold an any
that holds an int
) we have to remove ourselves from consideration:
struct any {
template <class T>
requires std::copy_constructible<std::decay_t<T>>
&& (!std::same_as<std::decay_t<T>, any>)
any(T&&);
any(any const&);
};
This is the same thing we had to do in C++17 and earlier when Scott Meyers wrote his book. At least, it's the same mechanism for resolving the problem - even if the syntax is better.
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.
Why is const template parameter not a universal/forwarding reference
Foreword: The official term is forwarding reference.
So why is the second case not a universal reference?
Because it is a reference to const. And references to const are not forwarding references.
why?
The whole point of a forwarding reference is that when an rvalue is given as an argument, the parameter will be deduced as reference to non-const rvalue which allows such argument to be moved from when forwarding (while simultaneously allowing lvalues to not be moved from). You cannot move from a reference to const because the argument of the move constructor will be an rvalue reference to non-const which cannot be bound to a reference to const.
The language-lawyer answer is: Because the standard says so:
[temp.deduct.call] A forwarding reference is an rvalue reference to a cv-unqualified template parameter that does not represent a template parameter of a class template ...
Related Topics
C Compiler Error from Standard Headers - Undefined C++ Definitions
In C++, What Is a "Namespace Alias"
How to Create Minidump for My Process When It Crashes
C++ Convert from 1 Char to String
Does "Const" Just Mean Read-Only or Something More
Reading from Ifstream Won't Read Whitespace
What Is the Performance Cost of Having a Virtual Method in a C++ Class
How to Take Screenshots of a Window with C++ in Windows
Intercepting/Rerouting Tcp Syn Packets to C++ Program in Linux
Computing Length of a C String at Compile Time. Is This Really a Constexpr
How Do Objects Work in X86 at the Assembly Level
What Default Promotions of Types Are There in the Variadic Arguments List
Extending the C++ Standard Library by Inheritance