Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

C macro _Generic gives unexpected compiler error

Using gcc.exe (Rev3, Built by MSYS2 project) 8.2.0.

I was trying to build a macro to automatically do type conversions between two types, where the two parameters should never be the same type. My problem is the compiler throws an error if I don't also include the same type case. What I wanted:

#include <stdio.h>
#include <stdint.h>
// Macro to return string based on two different types
#define bob( to, from ) \
    _Generic( to , \
    int32_t: _Generic(from,  \
      int16_t: "s-l", \
      int8_t:  "c-l" ) , \
    int16_t: _Generic(from, \
      int32_t: "l-s", \
      int8_t:  "c-s") , \
    int8_t:_Generic(from, \
      int32_t: "l-c",  \
      int16_t: "s-c")  \
    )

    void main(void)
    {
        int32_t i1;
        int16_t s1;
        int8_t  c1;

        printf("%s\n", bob(i1,s1));
        printf("%s\n", bob(i1,c1));
        printf("%s\n", bob(s1,c1));
        printf("%s\n", bob(s1,i1));
        printf("%s\n", bob(c1,s1));
        printf("%s\n", bob(c1,s1));

    }

$ gcc gbug.c -o gbug.exe
gbug.c: In function 'main':
gbug.c:23:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(i1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \
                   ^~~~
gbug.c:24:27: error: '_Generic' selector of type 'signed char' is not compatible with any association
     printf("%s\n", bob(i1,c1));
                           ^~
gbug.c:12:17: note: in definition of macro 'bob'
 int8_t:_Generic(from, \
                 ^~~~
gbug.c:25:27: error: '_Generic' selector of type 'signed char' is not compatible with any association
     printf("%s\n", bob(s1,c1));
                           ^~
gbug.c:12:17: note: in definition of macro 'bob'
 int8_t:_Generic(from, \
                 ^~~~
gbug.c:26:27: error: '_Generic' selector of type 'int' is not compatible with any association
     printf("%s\n", bob(s1,i1));
                           ^~
gbug.c:6:19: note: in definition of macro 'bob'
 int32_t: _Generic(from,  \
                   ^~~~
gbug.c:27:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(c1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \
                   ^~~~
gbug.c:28:27: error: '_Generic' selector of type 'short int' is not compatible with any association
     printf("%s\n", bob(c1,s1));
                           ^~
gbug.c:9:19: note: in definition of macro 'bob'
 int16_t: _Generic(from, \

This example is the simplest that I have found that will fail.

If I add in the "same type" conversion lines like this:

#define bob( to, from ) \
_Generic( to , \
int32_t: _Generic(from,  \
  int16_t: "s-l", \
  int32_t: "bug", \
  int8_t: "c-l" ) , \
int16_t: _Generic(from, \
  int32_t: "l-s", \
  int16_t: "bug", \
  int8_t: "c-s") , \
int8_t:_Generic(from, \
  int32_t: "l-c",  \
  int8_t: "bug", \
  int16_t: "s-c")  \
)

It build and runs with the expected result:

$ ./gbug.exe
s-l
c-l
c-s
l-s
s-c
s-c

verifying that I am not using the macro to expand any same type conditions. I understand _Generic is not a string substitution macro but I also thought that if you could use it without a default case it would correctly throw a compile error if you used an unknown type (or an unsupported combination of types, which is the behaviour I wanted) It is like the pre-processor is getting the two macro parameters mixed up.

Edit: So I have a better understanding, (See my answer below) but still looking to get the macro to throw a compile error if the two parameters are the same type. So far I have a trick to force a link error which is still better than a runtime error.

like image 893
bd2357 Avatar asked Mar 19 '19 18:03

bd2357


2 Answers

The problem is that every branch of a generic selection must be valid, even if they are not evaluated.

For example, your first macro:

bob(i1, s1)

Expands to (types added for clarity):

_Generic( ((int32_t) i1),
  int32_t: _Generic( ((int16_t) s1),
    int16_t: "s-l",
    int8_t:  "c-l" ),
  int16_t: _Generic( ((int16_t) s1),  // The error is here
    int32_t: "l-s",
    int8_t:  "c-s"),
  int8_t:_Generic( ((int16_t) s1),
    int32_t: "l-c",
    int16_t: "s-c")
)

Obviously the uint32_t branch is valid: It just selects "s-l". But the int16_t branch is not valid, as from (An int16_t itself) does not have a corresponding branch.

In this particular scenario, it wouldn't hurt to add a self-conversion operator that does nothing.

like image 97
Artyer Avatar answered Nov 11 '22 07:11

Artyer


A-Ha Moment, Thanks to John Bollinger in comments.

If I Hand expand the Macro into code:

void main(void)
{
    int32_t i1;
    int16_t s1;
    int8_t  c1;

    printf("%s\n", 
    _Generic( i1 , int32_t: _Generic(s1, int16_t: "s-l", int8_t: "c-l" ), 
                   int16_t: _Generic(s1, int32_t: "l-s", int8_t: "c-s" ),   // <-- No int16_t here
                   int8_t:  _Generic(s1, int32_t: "l-c", int16_t: "s-c") ) );

}

It becomes obvious that this will not compile unless the paths not taken are stripped out which I guess is not what happens.

So I guess default case to error condition is the correct method?

Edit: So I still have not figured out how to get the default case to throw a compiler error, however I discovered if I put a call in the default case to a non-existant function it will compile but will throw a linker error if I violate the rule I was trying to enforce. Not great but better than a runtime error.

char *this_function_does_not_exist(); // fake function prototype
#define bob( to, from ) \
    _Generic( to , \
    int32_t: _Generic(from,  \
      default: this_function_does_not_exist(), \
      int16_t: "s-l", \
      int8_t:  "c-l" ) , \
    int16_t: _Generic(from, \
      default: this_function_does_not_exist(), \
      int32_t: "l-s", \
      int8_t:  "c-s") , \
    int8_t:_Generic(from, \
      default: this_function_does_not_exist(), \
      int32_t: "l-c",  \
      int16_t: "s-c")  \
    )

If anyone who reads this has a better, C11, way to in effect embed a _Static_assert inside a _Generic let me know. (I know I can embed a _Generic inside a _Static_assert, it just gets really ugly and I don't want to maintain duplicate logic)

like image 2
bd2357 Avatar answered Nov 11 '22 07:11

bd2357