1

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)

Baum mit Augen
  • 49,044
  • 25
  • 144
  • 182
Ivan Rubinson
  • 3,001
  • 4
  • 19
  • 48

3 Answers3

3

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.

Passer By
  • 19,325
  • 6
  • 49
  • 96
  • 2
    `const T&` has the added benefit that it allows `int a; const int b; min(a, b)`: there is no question of whether `T` should deduce to `int` or `const int`. –  Aug 29 '18 at 11:58
  • 1
    @hvd Good point, is there an easy fix to the perfectly forwarded version? Or is it tedious? Or is it in fact not that big a deal? – Passer By Aug 29 '18 at 12:00
  • Proper version of `max`/`min` should have 2 template parameters SFINAEing out non-comparable versions. But I doubt the OP needs all that complexity. His question about literals not the max function, I believe – ixSci Aug 29 '18 at 12:02
  • @PasserBy I think you'd need to have two template parameters and return `std::common_type_t` to let that work. The SFINAE mentioned by ixSci should be implicitly handled by that already. –  Aug 29 '18 at 12:03
  • @hvd I'm wondering if `std::common_type` is correct. Ideally, the function should return a xvalue with two xvalues of the same type as arguments, which `std::common_type` doesn't support. But going through all the cases, I think it's just _way_ too tedious. – Passer By Aug 29 '18 at 12:09
  • @PasserBy I think you're right, I missed `common_type`'s decaying property. You'd need something like `std::common_type` except without that decay. Easily made yourself, but I don't know if the standard library has something readily available already. –  Aug 29 '18 at 12:14
  • @hvd Surprisingly, I think this handles everything. – Passer By Aug 29 '18 at 12:22
  • @PasserBy Do you still need `template const T& min(const T& a, const T& b)` once you have setup the perfect forwarding with types `T&&` and `U&&`? – vdavid Aug 29 '18 at 12:25
  • @vdavid No, it's there for exposition. – Passer By Aug 29 '18 at 12:25
  • @PasserBy Will `decltype(a < b ? std::forward(a) : std::forward(b))` be allowed if `a < b` can't give the answer at compile-time? – vdavid Aug 29 '18 at 12:27
  • @PasserBy Considering what you have is basically `std::common_type` without the decay, I'm not surprised. Nicely done :) –  Aug 29 '18 at 12:27
  • @vdavid Sure. The type of the expression does not depend on which operand is selected. `true ? 0 : ""` gives you a null pointer of type `const char *`, for instance. –  Aug 29 '18 at 12:28
  • This will return a *dangling reference* when a temporary is used. See [this answer](https://stackoverflow.com/a/667415/3893262). – O'Neil Aug 29 '18 at 13:30
  • @O'Neil I am well aware, but seeing as how `std::min` also returns references, I decided to follow the convention. – Passer By Aug 29 '18 at 13:57
2

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.

Geezer
  • 5,600
  • 18
  • 31
  • Which version of the standard begins allowing `T&&` syntax? – Ivan Rubinson Aug 29 '18 at 11:59
  • 1
    @IvanRubinson Since C++11. Added an edit to make clearer. – Geezer Aug 29 '18 at 12:01
  • You will have issue with `min(T&& a, T&& b)`, `a` and `b` should be deduced to the same type. `min(T&& a, U&& b)` would be valid though. – Jarod42 Aug 29 '18 at 13:07
  • @Jarod42 excuse me I mistyped the correction, in haste. Apologies. – Geezer Aug 29 '18 at 13:25
  • This will return a *dangling reference* when a temporary is used. See [this answer](https://stackoverflow.com/a/667415/3893262). – O'Neil Aug 29 '18 at 13:30
  • @O'Neil I actually think that because you initialize the reference with a pure rvalue, the life-time of the tempoary object gets extended to the life-time of the reference. thanks to the perfect forwarding this should stand. – Geezer Aug 29 '18 at 13:39
  • The life-time extension only stands inside the function. Leaving the function, the two parameters `a` and `b` are destroyed, and you're returning a dangling reference. – O'Neil Aug 29 '18 at 13:45
  • @O'Neil Oh I see, you are talking about this? http://eel.is/c++draft/class.temporary#6.10 – Geezer Aug 29 '18 at 13:47
  • @O'Neil if so then I'll edit immediately to reflect this, thus discarding with the first version and explain why. – Geezer Aug 29 '18 at 13:48
  • Almost, this is the previous one: [eel.is/c++draft/class.temporary#6.9](http://eel.is/c++draft/class.temporary#6.9). – O'Neil Aug 29 '18 at 13:54
  • Let us [continue this discussion in chat](https://chat.stackoverflow.com/rooms/179033/discussion-between-skepticalempiricist-and-oneil). – Geezer Aug 29 '18 at 14:08
0

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?

ixSci
  • 13,100
  • 5
  • 45
  • 79
  • But a `const` may be optmized to be "non living" – Ivan Rubinson Aug 29 '18 at 11:50
  • @IvanRubinson, well, not exactly. It might be placed into a read only section. So it will have an address but you won't be able to modify its value. Literals on the other hand do not exist while they aren't "materialized" (assigned to some variable) – ixSci Aug 29 '18 at 11:51