I'm using a library with a templated function that takes an rvalue reference, and for clarity, I am explicitly stating the template type instead of relying inference. I noticed that when the template type matches the argument type and the argument is an lvalue, I get a compiler error. Please see the example below, where the error is reproduced in the 3rd case.
#include <cstdlib>
#include <iostream>
template <class T>
void fnc(T &&value) {
std::cout << value << std::endl;
}
int main() {
int32_t var = 5;
// Call with no template type and lvalue: OK
fnc(var);
// Call with different template type and lvalue: OK
fnc<int16_t>(var);
// Call with same template type and lvalue: ERROR
// cannot bind rvalue reference of type ‘int&&’ to lvalue of type ‘int32_t’ {aka ‘int’}
fnc<int32_t>(var);
// Call with no template type and rvalue: OK
fnc(5);
// Call with different template type and rvalue: OK
fnc<int16_t>(5);
// Call with same template type and rvalue: OK
fnc<int32_t>(5);
return 0;
}
Note that on my machine, cstdlib
introduces typedef signed int int32_t
and typedef signed short int16_t
.
Why does this happen, and how can I work around it? I'd like to continue to be explicit with template types for safety.
From your own experiment, int16&&
can bind to a temporary int16
converted from an int32
l-value, but int32&&
cannot bind to an actual int32
l-value.
This has nothing to do with templates, see below:
void fnc16(int16_t&& value) {
std::cout << value << std::endl;
}
void fnc32(int32_t&& value) {
std::cout << value << std::endl;
}
int main() {
int32_t var = 5;
fnc16(var); // OK
fnc32(var); // ERROR
}
These are the rules as strange as they look.
Note for example that you can make it work by, creating an rvalue
fnc32(+var); // OK now
It is hard to understand what you want to achieve by specifying the template for a function template call instead of letting it deduce it for you. (Deduction is how perfect-forwarding works).
In any case, if you insist on suppressing the deduction, you have to use the right template type that can bind to an l-value reference, that is:
fnc<int32_t&>(var);
in your original code.
This is because reference collapsing: "int32_t& &&" -> int32_t&
, which is correct for var
(but not for +var
now).
In fact, any qualified argument would probably work <int32_t&>
, <int32_t&&>
, <int32_t const&>
, but not <int32_t>
.