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.
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.)
You're confusing rvalue references (like int&&
) and universal references (which are made from template 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&&
, deducing T = int (&)[5]
).
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With