Doc No: | SC22/WG14/N1404 |
---|---|
Date: | 2009-09-25 |
Reply to: | Clark Nelson <[email protected]> |
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.
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))
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.
__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:
<tgmath.h>
can be implemented without hard-coding them into the proposed
language facility. This suggests that they are sufficiently powerful to implement
a wide variety of reasonable library facilities.<tgmath.h>
, verifying implementations
by visual inspection can be made much easier if the default asssociation is
allowed to appear other than finally in the list of associations.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-expressiondefault :
assignment-expression
If WG14 approves of this approach, I intend to work on an implementation and the specification of it before the next meeting.