How to Handle Multiple Types in Max Without A Cast
Today I want to share with you an interesting technique shown by Arthur O’Dwyer in his CppCon talk, Template Normal Programming, to deal with min and max on different types. Arthur has kindly accepted that I share this content with you on Fluent C++.
This will be a shorter post, fit for summer as you must be busy with your summer project. Or, less likely, busy sipping a cocktail on the beach. Or both.
Template Normal Programming is a series of two talks covering everything about templates in C++, except template metaprogramming. The talks are well structured and very progressive, and all backed up with excerpts from the standard. That’s a pretty impressive job, and Arthur manages to keep a presentation style that’s easy to follow. A worthy investment of two hours of your time, in short.
Multiple types in a call to max
Now consider the following piece of code:
#include <algorithm> short f(); int main() { int x = 42; return std::max(f(), x); }
It doesn’t compile. Can you see why?
Take a moment to think about it! Can you see what is wrong with this piece of code?
Got it?
Consider the declaration of std::max
:
template< class T > constexpr const T& max( const T& a, const T& b );
Notice that there is only one template parameter T
.
And in the initial piece of code, the two parameters passed to std::max
are of different types: the first one is a short
and the second one is an int
. In a context without templates, short
and int
are implicitly convertible to one another. Meaning that if we had either this declaration:
int const& max(int const& a, int const& b);
or this one:
short const& max(short const& a, short const& b);
The code would have compiled fine.
But the fact that the type is a template prevents implicit conversions. Put a different way, the template type deduction must exactly match the passed types.
Two ways out
If you faced this compilation error in your code, how would you go about fixing it?
The first solution that comes to mind is to force one argument in, with a cast:
int m = std::max(static_cast<int>(f()), x);
It’s not pretty, but it does the job.
Instead, consider this other alternative: working around the template deduction by specifying the type explicitly:
int m = std::max<int>(f(), x);
You say to std::max
that T is int
, and not to worry about deducing it from the parameters. This way we’re out of a deduction context and implicit conversion of short
to int
can apply.
Now there is one good question about this technique: if the short
passed to std::max
is implicitly converted to an int
, this means that a temporary int
is created. And std::max
returns a reference to this temporary. Isn’t that a problem?
The answers depends on when the temporary is destroyed, and it is destroyed after the end of the full expression that contains it creation. And this full expression includes the copy of the value returned by std::max
into m
.
Here we’re good because we’re storing a copy (int
) of the value returned by std::max
. But had we stored a reference (int const&
) then it would have bound to a temporary and would thus be unusable on the following statement. Thanks to Björn and Arthur for helping me with these nuances.
Anyway, that’s one less cast in your code. And of course all of the above applies to min
too.
That’s it for this technique but there is much, much more to know about min and max in C++!
Related articles:
Don't want to miss out ? Follow:   Share this post!