"forwarding references" is in quotes because const
-qualified forwarding references aren't actually forwarding references, but I wanted to make it clear that I am specifically referring to function templates.
Take the following test functions (code is duplicated to avoid :
#include <iostream>
#include <type_traits>
#include <typeinfo>
using namespace std;
template <typename T, typename U> void print_types() {
cout << (is_const_v<remove_reference_t<T>> ? "const " : "")
<< typeid(T).name()
<< (is_rvalue_reference_v<T> ? " &&"
: is_lvalue_reference_v<T> ? " &"
: "")
<< ", " << (is_const_v<remove_reference_t<U>> ? "const " : "")
<< typeid(U).name()
<< (is_rvalue_reference_v<U> ? " &&"
: is_lvalue_reference_v<U> ? " &"
: "")
<< endl;
}
template <typename T> void print_rvalue_reference(T &&t) {
print_types<T, decltype(t)>();
}
template <typename T> void print_const_rvalue_reference(const T &&t) {
print_types<T, decltype(t)>();
}
int main() {
int i = 1;
const int j = 1;
print_rvalue_reference(1); // int, int &&
print_rvalue_reference(i); // int &, int &
print_rvalue_reference(j); // const int &, const int &
print_const_rvalue_reference(1); // int, const int &&
print_const_rvalue_reference(i); // error
print_const_rvalue_reference(j); // error
}
First, I wanted to note that print_rvalue_reference(j)
only works because print_rvalue_reference
never uses t
in a non-const
context. If this were not the case, the template instantiation would fail.
I am confused why the last two calls in main
cause errors during compilation. Reference collapsing allows using T = int &; const T &&
to become int &
and using T = const int &; const T &&
to become const int &
, which means print_const_rvalue_reference<int &>(i)
and print_const_rvalue_reference<const int &>(j)
are valid.
Why does print_const_rvalue_reference(j)
deduce T to be int
instead of const int &
when print_rvalue_reference(j)
does deduce T to be const int &
? Are (true) forwarding references special-cased to yield int &
instead of int
as part of its deduction guide?
CodePudding user response:
Actual forwarding references are indeed special-cased to deduce a reference type. See [temp.deduct.call]/3:
If
P
is a cv-qualified type, the top-level cv-qualifiers ofP
's type are ignored for type deduction. IfP
is a reference type, the type referred to byP
is used for type deduction. ... IfP
is a forwarding reference and the argument is an lvalue, the type “lvalue reference toA
” is used in place ofA
for type deduction.
Thus, in the case where P
is const T&&
(which is not a forwarding reference), it is transformed to const T
and whether or not the argument is an lvalue doesn't affect the type deduction, since value category is not part of the argument's type. Instead, the deduction process simply attempts to find T
such that const T
is the same type as the argument (possibly with additional cv-qualification). In the case of i
and j
, this means T
is deduced as int
and the argument type is const int&&
.
But in the special forwarding reference case, while P
is still transformed from T&&
to T
, the fact that it was originally a forwarding reference results in the replacement of an argument type by the corresponding lvalue reference type (if the argument is an lvalue). Thus, in the case of i
, the transformed argument type is int&
, and in the case of j
, it is const int&
. In both cases, T
is deduced in such a way as to make it identical to the transformed argument type, which means T
is deduced as a reference type.