0

I'm trying to follow this guide to implement the cbrt function in a type-generic way that works for float and double.

My C code:

#include <math.h>

/* based on https://web.archive.org/web/20131205042841/http://carolina.mff.cuni.cz/~trmac/blog/2005/the-ugliest-c-feature-tgmathh/ */
#define __has_integer_type(x) ((__typeof__(x))1.25 == 1)
#define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
#define __e2_old(x) (1 ? (int *)0 : (void *)(!__has_integer_type(x)))*/
#define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
#define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
#define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })

double my_cbrt1(double x) { return cbrt(x); }
double my_cbrt2(int x) { return cbrt(x); }
float my_cbrt3(float x) { return cbrt(x); } 

As indicated by https://godbolt.org/z/MW84rvca7 , GCC (gcc -std=c99) fails to compile it. I've tried several GCC versions ranging from 4.8 to 10.4. GCC 10.4 reports the following errors:

C source #1x86-64 gcc 10.4 (Editor #1)Output of x86-64 gcc 10.4 (Compiler #1)
<source>: In function 'my_cbrt1':
<source>:9:37: error: variable or field '__result' declared void
    9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
      |                                     ^~~~~~~~
<source>:11:36: note: in expansion of macro 'cbrt'
   11 | double my_cbrt1(double x) { return cbrt(x); }
      |                                    ^~~~
<source>: In function 'my_cbrt3':
<source>:9:37: error: variable or field '__result' declared void
    9 | #define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })
      |                                     ^~~~~~~~
<source>:13:34: note: in expansion of macro 'cbrt'
   13 | float my_cbrt3(float x) { return cbrt(x); }
      |                                  ^~~~

What am I doing wrong? Is the guide wrong, is my solution wrong, or does GCC implement the C99 standard incorrectly? How can it be fixed so that gcc -std=c99 compiles it?

Please note I'd like to fix the (void*) approach described in the guide above, and thus in this question I'm not interested in the following workarounds:

  • _Generic(...) with gcc -std=c11.
  • GCC __builtin_tgmath.
  • GCC __builtin_choose_expr.
  • GCC __builtin_classify_type. It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_classify_type.
  • GCC __builtin_types_compatible_p (see here). FYI It's indeed possible to solve it with a combination of __builtin_choose_expr, __typeof__ and __builtin_types_compatible_p.

FYI See How is <tgmath.h> implemented? for a similar question, about <tgmath.h>, but it doesn't answer my questions about this specific implementation and GCC.

How is <tgmath.h> implemented?

pts
  • 80,836
  • 20
  • 110
  • 183

1 Answers1

0

The reason for the failure is that GCC and Clang don't treat expressons containing floating point values as an integer constant expression, as demonstrated here:

prog.c: In function 'x':
prog.c:1:22: error: first argument to '__builtin_choose_expr' not a constant
 int x(void) { return __builtin_choose_expr(0.25 == 0.25, 5, 6); }
prog.c: In function 'x':
prog.c:1:22: error: first argument to '__builtin_choose_expr' not a constant
 int x(void) { return __builtin_choose_expr((int)(double)1, 5, 6); }

I don't know C99 well enough to decide whether GCC is correct here.

To fix it, the __has_integer_type(x) needs to be changed to something which doesn't contain floating-point numbers, not even as temporaries. Unfortunately this doesn't seem to be possible without __builtin_classify_type and __builtin_types_compatible_p.

However, with GCC __builtin_classify_type, it is easy (see also https://godbolt.org/z/43aYer9hE):

#include <math.h>

#define __has_integer_type(x) (__builtin_classify_type(x) != 8)
#define __e1(x) (1 ? (__typeof__(x) *)0 : (void *)__has_integer_type(x))
#define __e2_old(x) (1 ? (int *)0 : (void *)(!__has_integer_type(x)))*/
#define __e2(x) (1 ? (double *)0 : (void *)(!__has_integer_type(x)))
#define __result_type(x) __typeof__(*(1 ? (__typeof__(__e1(x)))0 : (__typeof__(__e2(x)))0))
#define cbrt(x) ({ __result_type(x) __result; if (sizeof(x) == sizeof(float) && !__has_integer_type(x)) { __result = cbrtf(x); } else { __result = cbrt(x); }; __result; })

double my_cbrt1(double x) { return cbrt(x); }
double my_cbrt2(int x) { return cbrt(x); }
float my_cbrt3(float x) { return cbrt(x); } 
pts
  • 80,836
  • 20
  • 110
  • 183
  • How can `0.25 == 0.25` have anything to do with it? The code in the question has only two `==` in it, one in `(__typeof__(x))1.25 == 1` and one in `sizeof(x) == sizeof(float)`. Neither of those can be macro-replaced to `0.25 == 0.25`. This is some other code not in the question. – Eric Postpischil Jun 12 '23 at 14:54
  • 1
    As for what the standard says, `0.25 == 0.25` would not be an *integer constant expression* as defined in C 1999 6.6 6 because that says a floating-point constant can be in an integer constant expression only as the immediate operand of a cast. So `(__typeof__(x))1.25 == 1` would be an integer constant expression, since `1.25` is the immediate operand of a cast, presuming the compiler treats its `__typeof__` extension as an allowable part of an integer constant expression. – Eric Postpischil Jun 12 '23 at 14:58
  • @EricPostpischil: Thank you for the info about how the C99 standard defines *integer constant expression*! GCC seems to implement it differently: neither `0.25 == 0.25` (shouldn't work) nor `(__typeof__(x))1.25 == 1` (should work) works in `__has_integer_type(...)` above. `sizeof(x) == sizeof(float)` works, but it is not useful there. PCC does allow `(__typeof__(x))1.25 == 1`. – pts Jun 12 '23 at 22:15
  • Nevertheless, the main point of the answer stands: it works iff `__has_integer_type(...)` expands to an *integer constant expression*. – pts Jun 12 '23 at 22:16
  • Actually, GCC may be correct here, because `x` may be a `double` (or `float`), an in this case, the value `1.2 remoains floating-point. – pts Jun 13 '23 at 00:04