The simplest way one can achieve this is to have separate functions for each type with a suffix to differentiate between them like the following:
max_i(); // int
max_ui(); // unsigned int
// So on for other types.
But that becomes too cumbersome to remember. One could instead opt for function-like macros:
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
But these have the disadvantage that arguments are evaluated twice. This can cause some trouble in the following calls:
MAX(rand(), *p++)
MIN(--a, b++)
But a comment preceding the macros:
/* XXX: Do not pass in arguments with side-effects to these macros. */
takes the burden away from the implementer, and shifts it onto the developer (assuming they were different people).
But these macros are still frowned upon. One way in which the double evaluation part can be avoided is by using C23's auto and GNU C's compound expression statement (supported by Clang and GCC, I am unaware about others):
#if defined(__GNUC__) || defined(__clang__)
#define MAX(a, b) ({ \
auto x = (a); \
auto y = (b); \
x > y ? x : y; })
#define MIN(a, b) ({ \
auto x = (a); \
auto y = (b); \
x < y ? x : y; })
#endif
These evaluate their arguments only once. But compound expression statement is not part of ISO C.
In which case we're only left with C11's _Generic:
#define MAX(a, b) _Generic((a) + (b), \
signed char: max_sc, \
unsigned char: max_uc, \
char: max_c, \
short int: max_si, \
unsigned short int: max_usi, \
int: max_i, \
unsigned int: max_ui, \
long int: max_li, \
unsigned long int: max_uli, \
long long int: max_lli, \
unsigned long long int: max_lli, \
float: max_f, \
double: max_d, \
long double: max_ld)(a, b)
#define MIN(a, b) _Generic((a) + (b), \
signed char: min_sc, \
unsigned char: min_uc, \
char: min_c, \
short int: min_si, \
unsigned short int: min_usi, \
int: min_i, \
unsigned int: min_ui, \
long int: min_li, \
unsigned long int: min_uli, \
long long int: min_lli, \
unsigned long long int: min_ulli, \
float: min_f, \
double: min_d, \
long double: min_ld)(a, b)
/* Or provide only the declarations here and have a corresponding source file
with all the definitions. */
#define gen_max(type, suffix) \
[[gnu::always_inline, gnu::const]] static inline max_##suffix(type a, type b) \
{ return a > b ? a : b; }
#define gen_min(type, suffix) \
[[gnu::always_inline, gnu::const]] static inline min_##suffix(type a, type b) \
{ return a > b ? a : b; }
gen_max(signed char, sc)
gen_max(unsigned char, uc)
gen_max(char, c)
gen_max(short int, si)
gen_max(unsigned short int, usi)
gen_max(int, i)
gen_max(unsigned int, ui)
gen_max(long int, li)
gen_max(unsigned long int, uli)
gen_max(long long int, lli)
gen_max(unsigned long long int, ulli)
gen_max(float, f)
gen_max(double, d)
gen_max(long double, ld)
gen_min(signed char, sc)
gen_min(unsigned char, uc)
gen_min(char, c)
gen_min(short int, si)
gen_min(unsigned short int, usi)
gen_min(int, i)
gen_min(unsigned int, ui)
gen_min(long int, li)
gen_min(unsigned long int, uli)
gen_min(long long int, lli)
gen_min(unsigned long long int, ulli)
gen_min(float, f)
gen_min(double, d)
gen_min(long double, ld)
This is both safe (doesn't evaluate the arguments twice), and Standard C code (unlike the second option).
Review Request:
In the third implementation, I have (a) + (b) as the controlling expression of _Generic, which would invoke undefined behaviour on signed integer overflow. How can I avoid that?
Would it be a good idea to merge these 3 implementations into a single source file, with this structure:
If __GNUC__ or __clang__ is defined, use the compound expression statement.
Else use C11's _Generic.
Unconditionally use MIN/MAX with a comment to warn against side-effects.
And perhaps name the previous ones MIN_SAFE/MIN_MAX.
? Do you see any other problems with the code, or difference between the implementations that I am overlooking?
typeof(a)could be used instead ofautotoo. \$\endgroup\$_Generic((a) + (b), the type will never be narrower thanint. \$\endgroup\$chartypes andsigned short intandunsigned short intthen. \$\endgroup\$signed char, code is also missing_Boolfrom the primative type list, yet with0*(a) + 0*(b), that can be ignored too. \$\endgroup\$