Emulating C11 compiler features with gcc: _Generic

Besides the new interfaces for threads and atomic operations that I already mentioned earlier, others of the new features that come with C11 are in reality already present in many compilers. Only that not all of them might agree upon the syntax, and especially not with the new syntax of C11. So actually emulating some these features is already possible and I implemented some of them in P99 on the base of what gcc provides: these are

  • static_assert (or _Static_assert) to make compile time assertions (a misnomer, again!)
  • alignof (or _Alignof) to get the alignment constraint for a type
  • alignas (of _Alignas) to constraint the alignment of objects or struct members. Only the variant that receives a constant expression is directly supported. The variant with a type argument can be obtained simply by alignas(alignof(T)).
  • noreturn (or _Noreturn) to specify that a function is not expected to return to the caller
  • thread_local (or _Thread_local) for thread local storage
  • _Generic for type generic expression.

The most interesting among them is probably the latter feature, type generic expressions.

Gcc already has three builtins that can be used to something like _Generic:

  • __typeof__(EXP) gives the type of the expression EXP
  • __builtin_types_compatible_p(T1, T2) is true if the two types are compatible
  • __builtin_choose_expr(CNTRL, EXP1, EXP2) choose between the two expressions at compile time.

This only gives binary decisions and not multiple ones like _Generic but with a good dose of P99_FOR we can come to something that resembles a lot. (I spare you the details of the implementation.) The syntax that we support is as follows

#include "p99_generic.h"

#define P99_GENERIC(EXP, DEF, ...)  <some nasty use of P99 and gcc builtins> 

For example as in the following

P99_GENERIC(a,
             , // empty default expression
             (int*, a),
             (double*, x));

That is an expression EXP, followed by a default value DEF, followed by a list of type value pairs. So here this is an expression that depending on the type of a will have a type of int* or double* that will be set to a or x, respectively.

In C11 syntax, the above would be coded with some kind of “label” syntax:

 _Generic(a,
          int*: a,
          double*: x);

As you can see above, the default value can be omitted. If so, it is replaced with some appropriate expression that should usually give you a syntax error.

Here is an example with a default expression that will be used when none of the types matches:

uintmax_t max_uintmax(uintmax_t, uintmax_t);
int       max_int(int, int);
long      max_long(long, long);
long long max_llong(long long, long long);
float     max_float(float, float);
double    max_double(double, double);

 a = P99_GENERIC(a + b,
                 max_uintmax,
                 (int, max_int),
                 (long, max_long),
                 (long long, max_llong),
                 (float, max_float),
                 (double, max_double))(a, b);

In C11 syntax

 a = _Generic(a + b,
              default: max_uintmax,
              int: max_int,
              long: max_long,
              long long: max_llong,
              float: max_float,
              double: max_double)(a, b);

Here all the expressions evaluate to a function specifier. If a + b is int, … or double the appropriate maximum function is chosen for that type. If none of these matches, the one for uintmax_t is chosen. The corresponding function is then evaluated with a and b as arguments.

  • Because the choice expression is a + b its type is the promoted common type of a and b. E.g for all types that are narrower than int, e.g short, normally int will be the type of the expression and max_int will be the function. If a would be unsigned and b would be double the result would be double as well.
  • The return type of the _Generic expression is a function to two arguments. If it would be for int, e.g, the type would be int ()(int, int). So the return type of the function call would be int in that case.
  • The arguments are promoted and converted to the expected type of the chosen function.

NB: if the compiler is already C11 complying, the P99_GENERIC expression will just be translated to the corresponding _Generic expression.
Otherwise only gcc and compatible compilers are supported.

Addendum: Today I checked out the new version of clang and discovered that their new version 3.1 that came out in December already supports _Generic directly. Well done.

Advertisement

8 thoughts on “Emulating C11 compiler features with gcc: _Generic”

  1. Just wanted to let you know P99_GENERIC rocks.
    Just one thing, it would be great if the default value would allow something to abort compiling, like

    #define X_field_off_msg(x)\
    ((struct X_field *)X_field_off_msg_(x))
    #define X_field_off_msg_(x)\
    P99_GENERIC(x,\
    #warning "error msg lacks field",\
    (struct msg_A *	, (x)->messages[0]->next),\
    (struct msg_B *	, (x)->messages[1]?(x)->messages[1]->next:NULL)\
    )
    

    currently I use assert("fail") as default which is of incorrect type, as it returns void, same behaviour, but feels wrong.

    Anyway, great to have this.

    1. You probably mean something like static_assert since this would be a compile time error, no?

      The idea of _Generic as by C11 is that there is automatically an error, if no default is given and none of the choices triggers. This should be the case here, too, since then you have an empty expression that messes up the syntax completely.

      To improve the error message one could in effect add a _Pragma. Something like

      _Pragma("message \"Invalid choice in type generic expression\"")
      

      Unfortunately giving diagnostics through pragmas is not standard, this version here would work for gcc and Co. But other compilers should at least give you a diagnostic for an unknown #pragma.

      1. Actually a solution with _Pragma will not work, because this always gives the diagnostic, regardless of the actual choice expression. But I found another solution that is a bit more sophisticated and uses a gcc extension, namely error attributes to functions. (Using gcc features is not a restriction since we are talking of the mapping of P99_GENERIC to gcc, anyhow.)

        I will push a new release with that solution later today.

  2. I created a snippet to demonstrate my problem with default values.
    For me, if there is no default value, it does not work at all, even matching generics end up being NULL instead.

    #include "p99_generic.h"
    
    struct field
    {
      void *next;
    };
    
    struct msg_B
    {
      struct field *fields[5];
    };
    
    struct msg_A
    {
      struct field *fields[4];
    };
    
    #define field_X_of_msg(x)                               \
      ((struct field *)                                     \
       P99_GENERIC(x,                                       \
                   assert("msg lacks field by definition"), \
                   (struct msg_A *, (x)->fields[0])         \
                   ))
    
    #define field_Y_of_msg(x)                       \
      ((struct field *)                             \
       P99_GENERIC(x,                               \
                   ,                                \
                   (struct msg_B *, (x)->fields[0]) \
                   ))
    
    int main(void) {
      struct msg_A A;
      struct msg_B B;
      struct field*fA=field_X_of_msg(&A);
      struct field*fB =field_Y_of_msg(&B);
    
      printf("%p %p\n",fA,fB);
    
      fB = field_Y_of_msg(&A);
      printf("%p %p\n",fA,fB);
    
      return 0;
    }
    
    gcc -Wall --std=c99 -Iinclude/ -o generic generic.c
    ./generic 
    0x7f70f1658c48 (nil)
    0x7f70f1658c48 0x7fff2394d620
    

    Now,
    0x7f70f1658c48 (nil) & 0x7f70f1658c48 0x7fff2394d620 is not what I expected, but maybe it is my expectations?
    Actually I’d expect this to fail during compiling, as you said the default is empty, and nothing matches, so fail?
    0x7f70f1658c48 (nil) is wrong to me as the generic for fB should have matched,
    0x7f70f1658c48 0x7fff2394d620, &A matches struct msg B *?

    It works when I use a default value, static_asserts do not work here, assert does, as the return value mismatches and can’t be casted properly.

      1. Thanks to you for testing and giving feedback. Glad that this works, now.

        For the values that you saw printed. These were the “correct” ones. You didn’t initialize your variables. The corresponding members that you printed were just uninitialized pointers. If I initialize the variables correctly (P99_INIT comes handy) it prints (nil) (nil) for the first print.

        Jens

        PS: please use [sourcecode][/sourcecode] tags when you include larger code snipsets in a comment

      2. I saw I was missing proper initialization, still the p99-2012-01-28 version was somewhat inconsistent:

        #include <p99/p99_generic.h>
        struct field
        {
        	void *next;
        };
        struct msg_B
        {
        	struct field *fields[5];
        };
        struct msg_A
        {
        	struct field *fields[4];
        };
        #define field_X_of_msg(x)\
        ((struct field *)P99_GENERIC(x,\
        (void),\
        (struct msg_A *, (x)->fields[0])\
        ))
        #define field_Y_of_msg(x)\
        ((struct field *)P99_GENERIC(x,\
        /* empty default */,\
        (struct msg_B *, (x)->fields[0])\
        ))
        
        int main(void)
        {
        	struct msg_A A;
        	struct msg_B B;
        	memset(&A,1,sizeof(struct msg_A));
        	memset(&B,2,sizeof(struct msg_B));
        	struct field *fA = field_X_of_msg(&A);
        	struct field *fB = field_Y_of_msg(&B);
        	printf("A %p B %p\n",&A, &B);
        	printf("fA %p fB %p\n",fA,fB);
        	fA = field_Y_of_msg(&A);
        	printf("fA %p fB %p\n",fA,fB);
        	return 0;
        }
        
        gcc -Wall --std=c99 -Ip99-2012-01-28/ -o generic generic.c 
        ./generic 
        A 0x7fffc1d9dbe0 B 0x7fffc1d9dbb0
        fA 0x101010101010101 fB 0x202020202020202
        fA 0x7fffc1d9dc10 fB 0x202020202020202
        

        I did not expect

        fA = field_Y_of_msg(&A);
        

        to compile, and the result returned by it was somewhat off my expectations too.

        Looking on the cpp results

        // cpp generic.c -Ip99-2012-01-28/
        fA = ((struct field *)__builtin_choose_expr (__builtin_types_compatible_p(__typeof__ (&A), struct msg_B *), ((&A)->fields[0]), &(const volatile struct { int p00_v; }){ .p00_v = 0 } ));
        

        I think the zero default value for 2012-01-28 was wrong, but well, it is fixed for me in 2012-01-30, which can be seen here:

        // cpp generic.c -Ip99-2012-01-30/
        fA = ((struct field *)__builtin_choose_expr (__builtin_types_compatible_p(__typeof__ (&A), struct msg_B *), ((&A)->fields[0]), p00_invalid_type_in_generic("generic.c" ":45" ": invalid type generic choice `" "&A" "` for " "(struct msg_B *, (&A)->fields[0])") ));
        

        While the error returned by gcc does not match the information cpp has

        generic.c:45:7: error: call to ‘p00_invalid_type_in_generic’ declared with attribute error: Invalid choice in type generic expression
        

        it’s enough to see something is wrong.

        Thanks again.

        Ah, not to forget, if you want to use the snippet for a regression test, it is all yours.

        – I would have used the tags, but I was unable to find a link for the syntax – maybe I would see a syntax menu bar if I had js on?

        1. I think the zero default value for 2012-01-28 was wrong, but well, it is fixed for me in 2012-01-30

          Yes, probably was too naive.

          While the error returned by gcc does not match the information cpp has

          It is very difficult to force gcc to spit out useful error diagnostics for all the cases. After all, this is just an emulation of the feature.

          What you see is the fallback diagnostic when P99_GENERIC is used as a statement or full expression. If it is used as part of another expression, you’d basically see what you have seen through the preprocessor.

          The diagnostic when it is used in a global declaration context is even worse. It just tells that there is a function call where a compile time expression is expected. I guess we have to live with that for the time being.

          – I would have used the tags, but I was unable to find a link for the syntax – maybe I would see a syntax menu bar if I had js on?

          No, in the menu you just have simple editing features, but not that one. This is one of the features of wordpress that you’d have to know about.

          Jens

Comments are closed.