The template function encountered an error when passed in a numeric literal, but string literals didn't

I'm writing this code

#include <iostream>
#include <string>


template <typename T>
void Print(T& value)
{
    std::cout << value << std::endl;
}

int main()
{
    Print("Hello");
    Print(1);
}

And when compiling, compilers told an error that “void Print<int>(T &)' : cannot convert argument 1 from 'int' to 'T &'". But the Print("Hello") didn't get an error. Why is that?

And I changed Print() function to

void Print(T value)
{
    std::cout << value << std::endl;
}

It worked. But I don't understand why the former code didn't work.


Case 1

Here we consider how Print(1); works.

In this case, the problem is that 1 is an rvalue and you're trying to bind that rvalue to a lvalue reference to nonconst T( that is, T&) which is not possible and hence the error. For example you cannot have:

void Print(int &value)
{
    std::cout << value << std::endl;
}

int main()
{
   Print(1);// won't work
}

Solution

So to solve your problem you can use a lvalue reference to const T(that is const T&) that can be bind to an rvalue, as shown below:

template <typename T>
void Print(const T& value)//note the const added here
{
    std::cout << value << std::endl;
}
int main()
{
   
    Print(1); //works now
}

Alternatively, you can also make the parameter as a rvalue reference to nonconst T(that is T&&).

template <typename T>
void Print(T&& value)//note the && added here
{
    std::cout << value << std::endl;
}
int main()
{
   
    Print(1); //this works too
}

Case 2

Here we consider the statement Print("Hello");

In this case, "Hello" is a string literal and has the type const char [6]. Also, the string literal "Hello" is an lvalue.

And we know that we can bind an lvalue to a lvalue reference to nonconst T(that is T&). So there is no error in this case. Also note that in this case the T is deduced to be const char [6].

Note

In case 2 above (Print("Hello");) there is no type decay because the argument is passed by reference and not by value.


Because of this:

test.cpp:45:11: error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
   45 |     Print(1);
      |           ^

So convert it to a universal reference:

template <typename T>
void Print( T&& value ) // notice &&, that's a universal reference, not an rvalue ref
{
    std::cout << value << std::endl;
}

1 is an rvalue, and so cannot bind to T&. It can bind to const T& though.

That is,

void Print(const T& value)

or

void Print(T&& value)

are the fixes.