Doc No: SC22/WG14/N1404
Date: 2009-09-25
Reply to: Clark Nelson <[email protected]>

General support for type-generic macros

This paper builds on N1340. When that paper was presented, Ulrich Drepper mentioned that the GNU compiler also implements features that can be used to implement type-generic macros. For the committe's consideration, those features are described here.

Prior art in GCC

GCC implements support for type-generic macros through some features which are presented as built-in functions.

__builtin_types_compatible_p is a predicate that tests whether two types are compatible, ignoring type qualification. (Because its arguments are syntactically not expressions but type-names, in practical terms, it must be implemented as a reserved word with a separate grammar.) It is possible to test the type of an expression by converting it to a type-specifier using typeof.

__builtin_choose_expr is analogous to a conditional-expression. It takes three arguments, the first of which is evaluated as a condition. The value of the result is that of the second or third argument, depending on the value of the first. The difference is that it is evaluated at compile time. As a result, the condition expression is required to be a constant expression, and the type of the result is simply that of the selected expression, without any promotion relative to the unselected expression. (Again, these semantics can't be modeled after a function, but in this case at least the syntax matches that of a function call.)

Here is an example from the GCC manual that shows how these features can be used to implement a type-generic macro:

#define foo(x)                                                   \
  __builtin_choose_expr (                                        \
    __builtin_types_compatible_p (typeof (x), double),           \
      foo_double (x),                                            \
      __builtin_choose_expr (                                    \
        __builtin_types_compatible_p (typeof (x), float),        \
          foo_float (x),                                         \
          /* The void expression results in a compile-time error \
             when assigning the result to something.  */         \
          (void)0))

Discussion

The facilities from GCC are extremely general, and are adequate to the purpose of implementing generic macros. (Full disclosure: the GNU library's <tgmath.h> is not implemented using these features, but I believe that's motivated by backward-compatibility considerations.)

However, there is some question whether it is really desirable to add the full generality of these facilities to the standard. They certainly feel like they would provide more than enough rope.

Also, the definition of a standard type-generic macro using these facilities would require lots of redundant and verbose boilerplate. The expression whose type is being tested needs to appear as many times as it is tested, which also turns out to be the number of adjacent closing parentheses at the end of the construct.

Proposal

__builtin_choose_expr is basically analogous to an if-statement. A type-generic macro can be implemented more conveniently by a construct more closely resembling a switch-statement. That is effectively what was proposed in N1340. I basically support that proposal, although I would like to make a few improvements to the syntax.

N1340 proposed a construct that resembles a function call, including a comma-separated list of associations between a type-name and a function. The associations are also separated by a comma; the effect is that a single delimiter is used at two different semantic levels of the construct. I propose instead to use a colon to separate the parts of an association.

N1340 also proposed that the list of associations be preceded by exactly three commas, and from one to three expressions for generic arguments (none of which is evaluated). I propose that there should be exactly one generic-argument expression, to eliminate the need for adjacent commas (or, from a different perspective, optional expressions). Combinations of types of different generic arguments can be handled by other means, as shown below.

Implementing the <tgmath.h> convention that generic arguments of integral type should be treated the same as those having type double requires some sort of special support. The simplest and most general form this support could take would be to allow an association that specifies a function to be called if none of the other types is matched. I propose that this should be indicated by using the keyword default in an association in place of a type-name.

The two most complicated type-generic math macros are fma, which takes three generic arguments (all of which must have real type, fortunately), and pow, which takes two generic arguments that can be either complex or real. Using the proposed facilities, I believe those macros could be implemented along the following lines:

#define _Tgm_cxrl(x, ldc, dc, fc, ld, d, f)     \
        _Generic(x,                             \
                long double _Complex:   ldc,    \
                double _Complex:        dc,     \
                float _Complex:         fc,     \
                long double:            ld,     \
                default:                d,      \
                float:                  f)
#define pow(x, y) \
    _Generic(y, \
long double _Complex: _Tgm_cxrl(x, cpowl, cpowl, cpowl, cpowl, cpowl, cpowl), \
double _Complex:      _Tgm_cxrl(x, cpowl, cpow , cpow , cpowl, cpow , cpow ), \
float _Complex:       _Tgm_cxrl(x, cpowl, cpow , cpowf, cpowl, cpow , cpowf), \
long double:          _Tgm_cxrl(x, cpowl, cpowl, cpowl,  powl,  powl,  powl), \
default:              _Tgm_cxrl(x, cpowl, cpow , cpow ,  powl,  pow ,  pow ), \
float:                _Tgm_cxrl(x, cpowl, cpow , cpowf,  powl,  pow ,  powf)) \
        (x, y)
#define sqrt(x) _Tgm_cxrl(x, csqrtl, csqrt, csqrtf, sqrtl, sqrt, sqrtf)(x)
#define _Tgm_real(x, ld, d, f)          \
        _Generic(x,                     \
                long double:    ld,     \
                default:        d,      \
                float:          f)
#define fma(x, y, z)                                    \
        _Tgm_real(x,                                    \
                _Tgm_real(y,                            \
                        _Tgm_real(z, fmal, fmal, fmal), \
                        _Tgm_real(z, fmal, fmal, fmal), \
                        _Tgm_real(z, fmal, fmal, fmal)),\
                _Tgm_real(y,                            \
                        _Tgm_real(z, fmal, fmal, fmal), \
                        _Tgm_real(z, fmal, fma , fma ), \
                        _Tgm_real(z, fmal, fma , fma )),\
                _Tgm_real(y,                            \
                        _Tgm_real(z, fmal, fmal, fmal), \
                        _Tgm_real(z, fmal, fma , fma ), \
                        _Tgm_real(z, fmal, fma , fmaf)))(x, y, z)

Three things should be noted from these examples:

Here is the grammar proposed. generic-selection would be added as a new alternative for primary-expression.

generic-selection:
_Generic ( assignment-expression , generic-association-list )
generic-association-list:
generic-association
generic-association-list , generic-association
generic-association:
type-name : assignment-expression
default : assignment-expression

If WG14 approves of this approach, I intend to work on an implementation and the specification of it before the next meeting.