I was using forwarding references in my code when I encountered something that I don't understand.
To illustrate the phenomenon I wrote this simplified version of my scenario:
#include <iostream>
template <typename T>
struct Cls
{
static void func(T& b)
{
std::cout << 1 << std::endl;
}
};
template <>
struct Cls<int>
{
static void func(int& b)
{
std::cout << 2 << std::endl;
}
};
template <typename T>
void some_func(T&& a)
{
Cls<T>::func(a);
}
int main()
{
int i{3};
some_func(i);
some_func(3);
}
My understanding of a universal references is that they can be either an lvalue
or an rvalue
.
(A) In my head, this means that the first call (some_func(i);
) should access a version of the function where the type of a
is T&
(in this particular case: int&
), since the passed variable i
is an lvalue
of type int
. Because T
is int
, and since there is a specialized function Cls<int>::func(int&)
, there should be a match and output should be 2
.
(B) The second call (some_func(3);
) should access a version where the type of a
is T&&
(in this case: int&&
). I was not entirely sure what the output here would be.
The actual output I got was:
> 1
> 2
Note that the behaviour is the same even for non-primitive types T
.
some_func()
depending on passed reference types? I.e., one for some_func(int& a)
and one for some_func(int&& a)
? Or is it actually some sort of new reference type within the same instantiated function?1
? Why is the specialization not used? Is my interpretation in (A) incorrect? In any case T
should be int
, so how is the generic implementation selected instead when they are otherwise identical?2
? Considering that the first call was unable to match with the specialization when the types are identical (int&
), why should it be able to match when there is a mismatch (int&
and int&&
)?a
is an xvalue
in both cases? I.e., that the rvalue
may be interpreted as an lvalue
because it is intermediately stored in a
? This would explain why the second call outputs 2
, but not why the first call does not?Thanks in advance.
Note that in your code Cls<T>::func(a);
the argument a
is always an lvalue reference and that's why functions taking lvalue reference are matched in both cases. In order to actually forward the reference you would need to use std::forward<T>(a)
, in which case the code wouldn't compile because there is no overload for rvalue argument.
The type T
in template <typename T> void some_func(T&& a);
is deduced as int&
for the first call some_func(i);
resulting in primary template instantiation Cls<int&>
. And in the function static void func(T& b)
the references are collapsed resulting in int&
argument type.
In the second call some_func(3)
the type T
is deduced as int
and therefore the specialization Cls<int>
is selected, and static void func(int& b)
is called as there is a direct match of the argument.
Here is a demo for all three cases.