Generic macros, countof()
May 24, 2023In addition to variadic macros, WCPL supports the generic
construct that is
traditionally used in so-called cover macros that expand to calls of different
functions based on types of their argument(s). The construct itself does not have
to be used in macros, although in practice it rarely appears anywhere else.
The usual C11 syntax for the generic
construct when used in a macro looks
something like this:
#define sign(x) _Generic((x), \
long long: signll(x), \
long: signl(x), \
default: signi(x) \
)
WCPL supports this syntax, provided that the default
association, if present,
is the last one in the list. The _Generic
keyword may also be spelled as generic
.
If C compatibility is not a concern, a WCPL alternative switch-like syntax can be used (note that newline-escaping backslashes are optional in BCPL):
#define sign(x) generic (x) {
case long long: signll(x);
case long: signl(x);
default: signi(x);
}
This syntax supports one extra WCPL-specific feature: generic dispatch based on
argument count. It uses the same countof(...)
construct that was described in the
previous posts:
#define ATAN(...) generic (countof(...)) {
case 1: atan(...);
case 2: atan2(...);
}
double foo(double x, double y, double z) {
// expands to atan2(y, x) + atan(z);
return ATAN(y, x) + ATAN(z);
}
The examples below come from WCPL’s <sys/intrs.h>
header:
#define _clz(x) (generic((x), \
unsigned long long: __builtin_clzll(x), \
long long: __builtin_clzll(x), \
default: __builtin_clz(x)))
#define _ctz(x) (generic((x), \
unsigned long long: __builtin_ctzll(x), \
long long: __builtin_ctzll(x), \
default: __builtin_ctz(x)))
#define _popcnt(x) (generic((x), \
unsigned long long: __builtin_popcountll(x), \
long long: __builtin_popcountll(x), \
default: __builtin_popcount(x)))
#define _rotl(x, n) (generic((x), \
unsigned long long: __builtin_rotleftll(x, n), \
long long: __builtin_rotleftll(x, n), \
default: __builtin_rotleft(x, n)))
#define _rotr(x, n) (generic((x), \
unsigned long long: __builtin_rotrightll(x, n), \
long long: __builtin_rotrightll(x, n), \
default: __builtin_rotright(x, n)))
#define _fasu(x) (generic((x), \
float: __builtin_asuint32(x)))
#define _dasull(x) (generic((x), \
double: __builtin_asuint64(x)))
#define _uasf(x) (generic((x), \
unsigned: __builtin_asfloat(x)))
#define _ullasd(x) (generic((x), \
unsigned long long: __builtin_asdouble(x)))
The last four macros use generic dispatch to ensure that no automatic promotions take place; the compiler will signal an error if a wrong-sized integer or floating-point expression is used as an argument to the corresponding builtin.