Ivan Rubinson
Ivan Rubinson

Reputation: 3361

Template accepts const but not literal

When writing a template, class T may be substituted by a const type.

Consider:

template<class T> T& min(T& a, T& b) {
    return a < b ? a : b;
}

This will work in the following cases:

int a = 1, b = 5;
const int c = 1, d = 5;
min(a, b); // T is int
min(c, d); // T is const int

But will throw a compilation error when called with a literal (like so):

min(1, 5); // T is const int literal

invalid initialization of non-const reference of type ‘int&’ from an rvalue of type ‘int’

Why? Isn't an int literal a const int? And how can the template be modified to allow working with literals?

(consistent with gcc 6.3 and MSVC 2015)

Upvotes: 1

Views: 179

Answers (3)

Geezer
Geezer

Reputation: 5720

Isn't an int literal a const int?

No, it is just an int, not const, and is defined as a prvalue, hence an lvalue reference cannot bind to it -- as is in your case.

Easily corrected by having the original template like this:

template<typename T>
const T& min(const T& a, const T& b){
    return a < b ? a : b;
}

as const T& will bind to rvalues as well.

Avoid changing to or adding anything the likes of this:

template<typename T, typename U>
auto&& min(T&& a, U&& b){
    return std::forward<T>(a < b ? a : b); 
}

as here we do not create a copy from the materialized temporary, and as such we're at the risk of returning a dangling reference. See here in [class.temporary]:

A temporary object bound to a reference parameter in a function call ([expr.call]) persists until the completion of the full-expression containing the call.

... at which point it dies. Hence the dangling-ness.

Upvotes: 2

Passer By
Passer By

Reputation: 21160

int literals have type int, not const int. T is therefore deduced to be int, and int& can't bind to a prvalue.

The correct way to write such a function is to either perfect forward the arguments, or use const T&, both of which can bind to anything.

template<typename T, typename U>
auto min(T&& a, U&& b) -> decltype(a < b ? std::forward<T>(a) : std::forward<U>(b))
{
    return a < b ? std::forward<T>(a) : std::forward<U>(b); 
}

// Or...
template<typename T>
const T& min(const T& a, const T& b)
{
    return a < b ? a : b;
}

In the case of perfectly forwarding the arguments, the two template parameters are needed in order for int a{}; min(a, 42); to compile, as their deduced types are different.

Upvotes: 3

ixSci
ixSci

Reputation: 13708

Literal produces a prvalue expression which T& can not accept. T& accepts only lvalues.

You can think about it this way: integral literal is a "non-living" thing as it has no address anywhere, how could you bind it to the lvalue reference and then modify it? Where that object would be located? Where the changes would be written?

Upvotes: 0

Related Questions