Skip to content

Generic macros, countof()

In 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.