Introduction
If you’ve programmed any length of time in C, you’ve likely written a macro that returns the minimum of two values like:
#define MIN(I,J) ((I) < (J) ? (I) : (J))
As I’ve explained in the macro article, all those parentheses should be used to ensure operator precedence as you expect. Even so, the problem of side effects occurring multiple times is still a problem:
int mf = MIN( f(), g() ); // either f or g called twice
int mn = MIN( ++i, j ); // undefined behavior
The second example above is worse because incrementing the same variable more than once in the same expression results in undefined behavior.
Functions
To eliminate side effects occurring more than once, a function can be used instead. But because C lacks templates like C++, multiple functions need to be written. At a minimum, you need:
static inline long long min_ll( long long j, long long j ) {
return i < j ? i : j;
}
static inline unsigned long long min_ull( unsigned long long j,
unsigned long long j ) {
return i < j ? i : j;
}
static inline double min_d( double i, double j ) {
return i < j ? i : j;
}
static inline long double min_ld( long double i, long double j ) {
return i < j ? i : j;
}
where min_ll will handle all signed integers, min_ull will handle all unsigned integers, min_d will handle float and double, and min_ld will handle long double.
We’re going to ignore
_Complex,_Decimal32,_Decimal64, and_Decimal128types._Complexis typically used only in specialized programs for math or physics, and the decimal types are typically used only in finance. If wanted, they’re left as trivial exercises for the reader.You could use
min_ldto handle bothfloatanddouble, butlong doubleis typically less performant than either, so casting all floating point values tolong doubleisn’t a good idea.
These should be declared inline and put into a header (.h) file. You can get away with making them static rather than having to use extern inline in a .c file since no self-respecting C compiler will refuse to inline such trivial functions at least with optimization.
If you want to reduce the verbosity, you can use a helper macro like:
#define MIN_IMPL(T,SUFFIX) \
static inline T min_##SUFFIX( T i, T j ) { return i < j ? i : j; }
MIN_IMPL(long long, ll)
MIN_IMPL(unsigned long long, ull)
MIN_IMPL(double, d)
MIN_IMPL(long double, ld)
#undef MIN_IMPL
_Generic
Additionally, _Generic can be used to give a veneer of function overloading so that we can simply write:
auto m = min( i, j );
and it will “just work” regardless of the types of i and j. Here’s the macro:
#define min(I,J) \
_Generic( (I) + (J), \
int : min_ll, \
long : min_ll, \
long long : min_ll, \
unsigned int : min_ull, \
unsigned long : min_ull, \
unsigned long long: min_ull, \
float : min_d, \
double : min_d, \
long double : min_ld \
)( (I), (J) )
The (I) + (J) is a common trick with _Generic to use the “usual arithmetic conversions” to determine the common type when the types of I and J are not the same. (As a reminder, _Generic does not evaluate the expression just like sizeof doesn’t.) The trick also promotes smaller integral types like char and short to int (or to unsigned int if char is unsigned) so we don’t need explicit cases for those.
Some astute readers might object and ask:
Doesn’t referencing function names in the expressions cause their addresses to be taken thereby preventing the compiler from inlining calls to them because they’re now being called via pointers-to-function?
Fortunately, no. Remember that _Generic happens at compile time, so in this case, the compiler substitutes the entire _Generic expression with a direct call to the chosen inline function.
Conclusion
Using _Generic and the usual arithmetic conversions, a minimal, type-safe, undefined-behavior-safe min function can be implemented in C.
Top comments (0)