This is a follow-up to Generic Max() for Integer Types (including Boolean) and An Attempt at Creating Generic Min()/Max() for Fundamental Types.
What's new:
MIN(), that was abandoned despondent, has been revived.MAX()and its tests were done with in the last post, this post implements a genericMIN()withMAX().- Again, floating point types haven't been entertained, and types narrower than
inthaven't been checked for (they are implicitly promoted tointby adding 0).
Code:
#ifndef MIN_H
#define MIN_H 1
#include <limits.h>
#include "max.h"
/* Current versions of gcc and clang support -std=c2x which sets
* __STDC_VERSION__ to this placeholder value. GCC 14.1 does not set
* __STDC_VERSION__ to 202311L with the std=c23 flag, but Clang 18.1 does. */
#define C23_PLACEHOLDER 202000L
#if defined(__STDC_VERSION__) && __STDC_VERSION >= C23_PLACEHOLDER
/* gnu::const is stricter than unsequenced, but it doesn't matter here. */
#define ATTRIB_CONST [[unsequenced]]
#elif defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
#define ATTRIB_CONST __attribute__((const))
#else
#define ATTRIB_CONST /**/
#endif
#if defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
#define ATTRIB_ALWAYS_INLINE __attribute__((always_inline))
#else
#define ATTRIB_ALWAYS_INLINE /**/
#endif
#define gen_basic_min(return_type, param_type1, param_type2, suffix) \
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline return_type min_##suffix(param_type1 a, param_type2 b) \
{ return a < b ? (return_type) a : (return_type) b; }
#define gen_nonbasic_min(return_type, param_type1, param_type2, cast_type, suffix) \
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline return_type min_##suffix(param_type1 a, param_type2 b) \
{ return max_##suffix(a, b) == a ? b : (cast_type) a; }
gen_basic_min(int, int, int, i_i)
gen_basic_min(long, int, long, i_li)
gen_basic_min(long long, int, long long, i_lli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_ui(int a, unsigned b)
{
return max_i_ui(a, b) == b ? a : (int) b;
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_uli(int a, unsigned long b)
{
return max_i_uli(a, b) == b ? a : (int) b;
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_i_ulli(int a, unsigned long long b)
{
return max_i_ulli(a, b) == b ? a : (int) b;
}
#define MIN_I(a, b) _Generic((b), \
int: min_i_i, \
unsigned int: min_i_ui, \
long int: min_i_li, \
unsigned long int: min_i_uli, \
long long int: min_i_lli, \
unsigned long long int: min_i_ulli)((a), (b))
gen_basic_min(unsigned, unsigned, unsigned, ui_ui)
gen_basic_min(unsigned, unsigned, unsigned long, ui_uli)
gen_basic_min(unsigned, unsigned, unsigned long long, ui_ulli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_ui_i(unsigned a, int b)
{
return min_i_ui(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_ui_li(unsigned a, long b)
{
#if LONG_MAX >= UINT_MAX
return max_ui_li(a, b) == b ? (long) a : b;
#else
return max_ui_li(a, b) == a ? b : (long) a;
#endif
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_ui_lli(unsigned a, long long b)
{
#if LLONG_MAX >= UINT_MAX
return max_ui_lli(a, b) == b ? (long long) a : b;
#else
return max_ui_lli(a, b) == a ? b : (long long) a;
#endif
}
#define MIN_UI(a, b) _Generic((b), \
int: min_ui_i, \
unsigned int: min_ui_ui, \
long int: min_ui_li, \
unsigned long int: min_ui_uli, \
long long int: min_ui_lli, \
unsigned long long int: min_ui_ulli)((a), (b))
gen_basic_min(long, long, int, li_i)
gen_basic_min(long, long, long, li_li)
gen_basic_min(long long, long, long long, li_lli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_ui(long a, unsigned b)
{
return min_ui_li(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_uli(long a, unsigned long b)
{
return max_li_uli(a, b) == b ? a : (long) b;
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_li_ulli(long a, unsigned long long b)
{
return max_li_ulli(a, b) == b ? a : (long) b;
}
#define MIN_LI(a, b) _Generic((b), \
int: min_li_i, \
unsigned int: min_li_ui, \
long int: min_li_li, \
unsigned long int: min_li_uli, \
long long int: min_li_lli, \
unsigned long long int: min_li_ulli)((a), (b))
gen_basic_min(unsigned, unsigned long, unsigned, uli_ui)
gen_basic_min(unsigned long, unsigned long, unsigned long, uli_uli)
gen_basic_min(unsigned long, unsigned long, unsigned long long, uli_ulli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_uli_i(unsigned long a, int b)
{
return min_i_uli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_uli_li(unsigned long a, long b)
{
return min_li_uli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_uli_lli(unsigned long a, long long b)
{
#if LLONG_MAX >= ULONG_MAX
return max_uli_lli(a, b) == b ? (long long) a : b;
#else
return max_uli_lli(a, b) == a ? b : (long long) a;
#endif
}
#define MIN_ULI(a, b) _Generic((b), \
int: min_uli_i, \
unsigned int: min_uli_ui, \
long int: min_uli_li, \
unsigned long int: min_uli_uli, \
long long int: min_uli_lli, \
unsigned long long int: min_uli_ulli)((a), (b))
gen_basic_min(long long, long long, int, lli_i)
gen_basic_min(long long, long long, long, lli_li)
gen_basic_min(long long, long long, long long, lli_lli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_ui(long long a, unsigned b)
{
return min_ui_lli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_uli(long long a, unsigned long b)
{
return min_uli_lli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_lli_ulli(long long a, unsigned long long b)
{
return max_lli_ulli(a, b) == b ? a : (long long) b;
}
#define MIN_LLI(a, b) _Generic((b), \
int: min_lli_i, \
unsigned int: min_lli_ui, \
long int: min_lli_li, \
unsigned long int: min_lli_uli, \
long long int: min_lli_lli, \
unsigned long long int: min_lli_ulli)((a), (b))
gen_basic_min(unsigned, unsigned long long, unsigned, ulli_ui)
gen_basic_min(unsigned long, unsigned long long, unsigned long, ulli_uli)
gen_basic_min(unsigned long long, unsigned long long, unsigned long long, ulli_ulli)
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline int min_ulli_i(unsigned long long a, int b)
{
return min_i_ulli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long min_ulli_li(unsigned long long a, long b)
{
return min_li_ulli(b, a);
}
ATTRIB_ALWAYS_INLINE ATTRIB_CONST static inline long long min_ulli_lli(unsigned long long a, long long b)
{
return min_lli_ulli(b, a);
}
#define MIN_ULLI(a, b) _Generic((b), \
int: min_ulli_i, \
unsigned int: min_ulli_ui, \
long int: min_ulli_li, \
unsigned long int: min_ulli_uli, \
long long int: min_ulli_lli, \
unsigned long long int: min_ulli_ulli)((a), (b))
#define MIN(a, b) _Generic((a) + 0, \
int: MIN_I((a) + 0, (b) + 0), \
unsigned int: MIN_UI((a) + 0, (b) + 0), \
long int: MIN_LI((a) + 0, (b) + 0), \
unsigned long int: MIN_ULI((a) + 0, (b) + 0), \
long long int: MIN_LLI((a) + 0, (b) + 0), \
unsigned long long int: MIN_ULLI((a) + 0, (b) + 0))
#undef ATTRIB_ALWAYS_INLINE
#undef ATTRIB_CONST
#undef C23_PLACEHOLDER
#endif /* MIN_H */
And here are some tests:
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include "min.h"
/* Current versions of gcc and clang support -std=c2x which sets
* __STDC_VERSION__ to this placeholder value. GCC 14.1 does not set
* __STDC_VERSION__ to 202311L with the std=c23 flag, but Clang 18.1 does. */
#define C23_PLACEHOLDER 202000L
#if defined(__STDC_VERSION__) && __STDC_VERSION >= C23_PLACEHOLDER
#define NORETURN [[noreturn]]
#elif defined(_MSC_VER)
#define NORETURN __declspec(noreturn)
#elif defined(__GNUC__) || defined(__clang__) || defined(__INTEL_LLVM_COMPILER)
#define NORETURN __attribute__((noreturn))
#else
#define NORETURN _Noreturn
#endif
NORETURN static void cassert(const char cond[static 1],
const char file[static 1],
size_t line)
{
fflush(stdout);
fprintf(stderr, "Assertion failed: '%s' at %s, line %zu.\n", cond, file, line);
exit(EXIT_FAILURE);
}
#define test(cond) do { \
if (!(cond)) { cassert(#cond, __FILE__, __LINE__); } } while (false)
/* Get the name of a type */
#define ctypename(x) _Generic((x), \
_Bool: "_Bool", \
unsigned char: "unsigned char", \
char: "char", \
signed char: "signed char", \
short: "short", \
unsigned short: "unsigned short", \
int: "int", \
unsigned: "unsigned", \
long: "long", \
unsigned long: "unsigned long", \
long long: "long long", \
unsigned long long: "unsigned long long", \
default: "other")
static const int imax = INT_MAX;
static const unsigned umax = UINT_MAX;
static const long lmax = LONG_MAX;
static const unsigned long ulmax = ULONG_MAX;
static const long long llmax = LLONG_MAX;
static const unsigned long long ullmax = ULLONG_MAX;
static const long lmin = LONG_MIN;
static const int imin = INT_MIN;
static const long long llmin = LLONG_MIN;
static void test_min_int(void)
{
test(MIN(3, 5) == 3 && strcmp(ctypename(MIN(3, 5)), "int") == 0);
test(MIN(5, 3) == 3 && strcmp(ctypename(MIN(5, 3)), "int") == 0);
test(MIN(imax, imin) == imin && strcmp(ctypename(MIN(imax, imin)), "int") == 0);
test(MIN(imin, imax) == imin && strcmp(ctypename(MIN(imin, imax)), "int") == 0);
test(MIN(0, imax) == 0 && strcmp(ctypename(MIN(0, imax)), "int") == 0);
test(MIN(0, imin) == imin && strcmp(ctypename(MIN(0, imin)), "int") == 0);
}
static void test_min_unsigned_int(void)
{
test(MIN(3U, 5U) == 3U && strcmp(ctypename(MIN(3U, 5U)), "unsigned") == 0);
test(MIN(5U, 3U) == 3U && strcmp(ctypename(MIN(5U, 3U)), "unsigned") == 0);
test(MIN(umax, 0U) == 0U && strcmp(ctypename(MIN(umax, 0U)), "unsigned") == 0);
}
static void test_min_long(void)
{
test(MIN(3L, 5L) == 3L && strcmp(ctypename(MIN(3L, 5L)), "long") == 0);
test(MIN(5L, 3L) == 3L && strcmp(ctypename(MIN(5L, 3L)), "long") == 0);
test(MIN(lmax, lmin) == lmin && strcmp(ctypename(MIN(lmax, lmin)), "long") == 0);
test(MIN(lmin, lmax) == lmin && strcmp(ctypename(MIN(lmin, lmax)), "long") == 0);
test(MIN(0L, lmax) == 0L && strcmp(ctypename(MIN(0L, lmax)), "long") == 0);
test(MIN(0L, lmin) == lmin && strcmp(ctypename(MIN(0L, lmin)), "long") == 0);
}
static void test_min_unsigned_long(void)
{
test(MIN(3UL, 5UL) == 3UL && strcmp(ctypename(MIN(3UL, 5UL)), "unsigned long") == 0);
test(MIN(5UL, 3UL) == 3UL && strcmp(ctypename(MIN(5UL, 3UL)), "unsigned long") == 0);
test(MIN(ulmax, 0UL) == 0UL && strcmp(ctypename(MIN(ulmax, 0UL)), "unsigned long") == 0);
test(MIN(0UL, ulmax) == 0UL && strcmp(ctypename(MIN(0UL, ulmax)), "unsigned long") == 0);
}
static void test_min_long_long(void)
{
test(MIN(3LL, 5LL) == 3LL && strcmp(ctypename(MIN(3LL, 5LL)), "long long") == 0);
test(MIN(5LL, 3LL) == 3LL && strcmp(ctypename(MIN(5LL, 3LL)), "long long") == 0);
test(MIN(llmax, llmin) == llmin && strcmp(ctypename(MIN(llmax, llmin)), "long long") == 0);
test(MIN(llmin, llmax) == llmin && strcmp(ctypename(MIN(llmin, llmax)), "long long") == 0);
test(MIN(0LL, llmax) == 0LL && strcmp(ctypename(MIN(0LL, llmax)), "long long") == 0);
test(MIN(0LL, llmin) == llmin && strcmp(ctypename(MIN(0LL, llmin)), "long long") == 0);
}
static void test_min_unsigned_long_long(void)
{
test(MIN(3ULL, 5ULL) == 3ULL && strcmp(ctypename(MIN(3ULL, 5ULL)), "unsigned long long") == 0);
test(MIN(5ULL, 3ULL) == 3ULL && strcmp(ctypename(MIN(5ULL, 3ULL)), "unsigned long long") == 0);
test(MIN(ullmax, 0ULL) == 0ULL && strcmp(ctypename(MIN(ullmax, 0ULL)), "unsigned long long") == 0);
test(MIN(0ULL, ullmax) == 0ULL && strcmp(ctypename(MIN(0ULL, ullmax)), "unsigned long long") == 0);
}
static void test_min_mixed(void)
{
test(MIN(3, 5U) == 3 && strcmp(ctypename(MIN(3, 5U)), "int") == 0);
test(MIN(5U, 3) == 3 && strcmp(ctypename(MIN(5U, 3)), "int") == 0);
test(MIN(3L, 5) == 3L && strcmp(ctypename(MIN(3L, 5)), "long") == 0);
test(MIN(5, 3L) == 3L && strcmp(ctypename(MIN(5, 3L)), "long") == 0);
test(MIN(3LL, 5) == 3LL && strcmp(ctypename(MIN(3LL, 5)), "long long") == 0);
test(MIN(5, 3LL) == 3LL && strcmp(ctypename(MIN(5, 3LL)), "long long") == 0);
test(MIN(3ULL, 5) == 3ULL && strcmp(ctypename(MIN(3ULL, 5)), "int") == 0);
test(MIN(5, 3ULL) == 3ULL && strcmp(ctypename(MIN(5, 3ULL)), "int") == 0);
test(MIN(3UL, 5U) == 3UL && strcmp(ctypename(MIN(3UL, 5U)), "unsigned") == 0);
test(MIN(5U, 3UL) == 3UL && strcmp(ctypename(MIN(5U, 3UL)), "unsigned") == 0);
}
int main(void)
{
test_min_int();
test_min_unsigned_int();
test_min_long();
test_min_unsigned_long();
test_min_long_long();
test_min_unsigned_long_long();
test_min_mixed();
return EXIT_SUCCESS;
}
The code compiles cleanly under Clang 18.1 with a strict set of warnings (-Wconversion included), and these tests all pass.
Both of these files are present here: utils.
Review Request:
Bugs, wrong comparisons, redundant comparisons, portability. Any missing types?
Edit: The code was compiled with Clang 18.1 on a Linux Mint 21.2 x86_64 virtual machine with the following compiler flags:
clang-18 -std=c2x -g3 -ggdb -no-pie -Wall -Wextra -Warray-bounds -Wconversion -Wformat-signedness -Wmissing-braces -Wno-parentheses -Wpedantic -Wstrict-prototypes -Wwrite-strings -Winline -s -fno-builtin -fno-common -fno-omit-frame-pointer -fsanitize=float-cast-overflow -fsanitize=address -fsanitize=undefined -fsanitize=leak max_test.c -o max_test
It emitted no warnings, except that Wformat-signedness is an unknown warning option.