Search code examples
c++objective-ccmath.htgmath

What does __tg_promote do in tgmath.h


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?


Solution

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