Home > other >  Why can't lvalue references bind to const "forwarding references"?
Why can't lvalue references bind to const "forwarding references"?

Time:11-16

"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 of P's type are ignored for type deduction. If P is a reference type, the type referred to by P is used for type deduction. ... 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.

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.

  • Related