1

I'm looking at tgmath.h and trying to understand exactly how it selects the correct function based on the size of the input value.

The special sauce seems to be this __tg_promote macro but the deeper I dig the deeper this puzzle gets. Does anyone have a short answer to what __tg_promote actually does?

Awesome-o
  • 2,002
  • 1
  • 26
  • 38

1 Answers1

2

In clang's implementation of tgmath.h it appears that __tg_promote is in fact a function, and not a macro. The definition can be found here.

typedef void _Argument_type_is_not_arithmetic;
static _Argument_type_is_not_arithmetic __tg_promote(...)
  __attribute__((__unavailable__,__overloadable__));
static double               _TG_ATTRSp __tg_promote(int);
static double               _TG_ATTRSp __tg_promote(unsigned int);
static double               _TG_ATTRSp __tg_promote(long);
static double               _TG_ATTRSp __tg_promote(unsigned long);
static double               _TG_ATTRSp __tg_promote(long long);
static double               _TG_ATTRSp __tg_promote(unsigned long long);
static float                _TG_ATTRSp __tg_promote(float);
static double               _TG_ATTRSp __tg_promote(double);
static long double          _TG_ATTRSp __tg_promote(long double);
static float _Complex       _TG_ATTRSp __tg_promote(float _Complex);
static double _Complex      _TG_ATTRSp __tg_promote(double _Complex);
static long double _Complex _TG_ATTRSp __tg_promote(long double _Complex);

It's a function with multiple overloads (not allowed in C in general) and no definition, which is fine because it's never actually called! __tg_promote is only used to determine the type that a numeric type should be promoted to. (Integral types to double; floating point types to themselves.) This is clear when you look at the next few macros:

#define __tg_promote1(__x)           (__typeof__(__tg_promote(__x)))
#define __tg_promote2(__x, __y)      (__typeof__(__tg_promote(__x) + \
                                                 __tg_promote(__y)))
#define __tg_promote3(__x, __y, __z) (__typeof__(__tg_promote(__x) + \
                                                 __tg_promote(__y) + \
                                                 __tg_promote(__z)))

The __tg_promote function isn't being called because it occurs inside the compiler-specific __typeof__ macro. The __tg_promote1 macro simply expands to the promoted type of its argument, within parentheses. __tg_promote2 expands to the type (again parenthesis-enclosed) that would result if two values of the promoted types of its arguments were added. So for example, __tg_promote2(0.0f, 0) would be (double), since adding a float and a double (result of promoting int) gives a double. __tg_promote3 is similar.

The remainder of the header consists of overloaded definitions of functions that delegate to the respective ordinary C functions:

// atan2

static float
    _TG_ATTRS
    __tg_atan2(float __x, float __y) {return atan2f(__x, __y);}

static double
    _TG_ATTRS
    __tg_atan2(double __x, double __y) {return atan2(__x, __y);}

static long double
    _TG_ATTRS
    __tg_atan2(long double __x, long double __y) {return atan2l(__x, __y);}

In order to be able to call, say, atan2(1.0f, 1) we need to be able to delegate to __tg_atan2(double, double). This is where __tg_promote2 comes in to determine that when we have one float argument and one int argument, both should be converted to double:

#define atan2(__x, __y) __tg_atan2(__tg_promote2((__x), (__y))(__x), \
                                   __tg_promote2((__x), (__y))(__y))

So in this case __tg_promote2((__x), (__y)) expands to (double) and we get __tg_atan2((double)(__x), (double)(__y)), which is exactly what we want.

Brian Bi
  • 111,498
  • 10
  • 176
  • 312