Search code examples
c++templatesrvalue-referencervaluelvalue

Providing a template type of the same type as argument causes an error for rvalue reference function


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.


Solution

  • 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
    }
    

    https://godbolt.org/

    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>.