Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why do arrays of const preferentially bind to const T& parameters instead of T&& parameters?

Tags:

c++

c++11

It's generally a bad idea to overload a function template taking a "T&&" parameter, because that can bind to anything, but let's suppose we do it anyway:

template<typename T>
void func(const T& param)
{
  std::cout << "const T&\n";
}

template<typename T>
void func(T&& param)
{
  std::cout << "T&&\n";
}

My understanding has been that the const T& overload will get called for arguments that are const lvalues, and the T&& overload will get called for all other argument types. But consider what happens when we call func with arrays of const and non-const contents:

int main()
{
  int array[5] = {};
  const int constArray[5] = {};

  func(array);             // calls T&& overload
  func(constArray);        // calls const T& overload
}

VC10, VC11, and gcc 4.7 agree on the results shown. My question is why the second call invokes the const T& overload. The simple answer is that constArray has a const in it, but I think that's too simple. The type T that is deduced (regardless of the template selected) is "array of 5 const ints" so the type of param in the const T& overload would be "reference to const array of 5 const ints". But the array named constArray is not itself declared const. So why doesn't the call to func(constArray) invoke the T&& overload, thus yielding a type for param of "reference to array of 5 const ints"?

This question is motivated by the discussion associated with the question at c++ template function argument deduce and function resolution, but I think that thread got sidetracked on other issues and did not clarify the question I'm now asking here.

like image 506
KnowItAllWannabe Avatar asked Sep 11 '12 17:09

KnowItAllWannabe


2 Answers

In function parameter lists (as well as everywhere else), a cv qualification on an array type is shuffled right to qualify the array element type. For example, with T = int [5], const T & is converted to int const (&) [5].

3.9.3 CV-qualifiers [basic.type.qualifier]

2 - [...] Any cv-qualifiers applied to an array type affect the array element type, not the array type (8.3.4).

So the call to func with an argument of type int const [5] is deduced as a call to either of:

void func<int [5]>(int const (&) [5])
void func<int const (&) [5]>(int const (& &&) [5])
// where the above collapses to
// 'void func<int const (&) [5]>(int const (&) [5])'

Both overloads are viable, but the former is preferred:

Let T1 be the const T & template and T2 be the T && template; that is, their parameter types are T1 := const T & and T2 := T &&. Then the transformed argument types (14.5.6.2:3) can be written A1 := const C &, A2 := D && for synthesized types C, D.

Now, we attempt to order T1 against T2 (14.8.2.4:2), first using A1 as the argument template and P2 as the parameter template. We remove references (14.8.2.4:5) giving A1 -> const C and T2 -> T, then remove cv-qualification (14.8.2.4:7) giving A1 -> C and T2 -> T. The template T can be deduced to C (14.8.2.4:8) so A1 is at least as specialised as P2; conversely, A2 -> D -> D, P1 -> const T -> T, and T can be deduced to D, so A2 is at least as specialised as P1.

This would usually imply that neither is more specialised than the other; however, because the P and A types are reference types 14.8.2.4:9 applies, and since A1 is an lvalue reference and P2 is not, T1 is considered more specialized than T2. (A tie between reference types can also be broken by cv-qualification under the same clause.)

like image 177
ecatmur Avatar answered Oct 28 '22 05:10

ecatmur


You're confusing rvalue references (like int&&) and universal references (which are made from temp­late parameters, as in template <typename T> ... T&&).

Rvalue references indeed don't bind to lvalues. But universal references bind to anything. The question is just who's a better match.

The type you have is int const [5]. Now let's see:

  • against T const &: Matches with T = int[5].

  • against T &&: Matches with T = int const (&)[5].

The former is a better match, in the following sense: Both templates produce identical overloads. But T = int[5] is more specialised than T = int const (&)[5]. You can see this because T = int const (&)[5] can be realised as T = U const & with U = int[5].

Note that to bind an lvalue to a universal reference, the type itself has to be deduced as a reference type.

(Obviously array doesn't match const T &, because it is not const. It can only match T&&, dedu­cing T = int (&)[5]).

like image 31
Kerrek SB Avatar answered Oct 28 '22 05:10

Kerrek SB