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); } 1 2 3 4 5 6 7 8 9 #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 ); 1 2 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); 1 int const & max ( int const & a , int const & b ) ;

or this one:

short const& max(short const& a, short const& b); 1 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); 1 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); 1 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:

Share this post! Don't want to miss out ?