In the company I am working we are trying to replace all calls to the
max() macro with a call to our own macro since the template solution
requires identical types at both sides.
An overloaded function, or template function, is not constrained to use the
same type for all parameters.
The real problem is that the template cannot instantiate a return type based on
the types of the arguments.
You cannot encode your own custom arithmetic promotion rules. The template
cannot analyze its instantiated arguments and decide, ``aha, we have a double X
int combination here, so the int side promotes to double and the return type is
double''.
The return type is either fixed, or a template parameter. If it's a template
parameter, it must appear in template function's argument list (but we could
work around this restriction by making max a template class).
If you use a macro, it inherits the promotion rules from the build-in language
construct, so you win there. But you're also bringing in multiple evaluation of
the arguments.
This does indicate indicates that templates have some shortcomings. A template
function is not the right tool for implementing max.
But show me your max preprocessor macro that avoids multiple evaluation of A
and B, takes only the values as parameters (no type name hints) that can be
called using one name for all combinations, and doesn't use any GNU C or other
extensions like ``typeof''.
If you write:
max(2,3.5)
the template solution will not work.
You are better off writing a set of overloaded functions, where you can craft
the return value by hand. See below.
This means that a colleague of mine is working since 2-3 weeks making
hundreds of modifications to a huge C++ code base. Making max() a
template was a BIG MISTAKE.
Obviously, the template-based max, limited as it is, worked in these hundreds
of places before! Your colleague ran into a snag and is now retargetting code.
But, even if you make max a template, it should be easy to revert it to a set
of overloads.
Your code should just be calling max(x, y), allowing the template to deduce and
expand the instantiations.
You can throw the template out and hand-roll a bunch of overloads for max.
#include <cstdio>
#define MAX_IMPL(a, b) ((a) > (b) ? (a) : (b))
double max(double a, int b)
{
return MAX_IMPL(a, b);
}
double max(int a, double b)
{
return MAX_IMPL(a, b);
}
int max(int a, int b)
{
return MAX_IMPL(a, b);
}
int main()
{
double m1 = max(3.0, 4);
double m2 = max(3.0, 2);
double m3 = max(3, 4.0);
double m4 = max(3, 2.0);
int m5 = max(5, 3);
printf("%f %f %f %f %i\n", m1, m2, m3, m4, m5);
}
Output:
4.000000 3.000000 4.000000 3.000000 5
I can't imagine what kind of a mess your colleagues could have created with a
template-based max() that couldn't be cured like this. Maybe the code didn't
rely on template argument deduction, so it was littered with explicit template
argument lists that must now be gone? What was the actual issue?
On the other hand, I used a macro as a labor-saving device above; it's not
intended to be used by everyone. The MAX_IMPL macro in my code cannot be
replaced by a regular function (that leads us to regress, since we are trying
to use it to implement a function). The MAX_IMPL macro cannot be replaced by
single template function, because of variance in the return value. There is no
way in standard C++ of avoiding repetition of (a > b ? (a) : (b)) which is more
convenient than a preprocessor macro.