Макрос C _Generic выдает неожиданную ошибку компилятора - PullRequest
7 голосов
/ 19 марта 2019

Использование gcc.exe (Rev3, созданный проектом MSYS2) 8.2.0.

Я пытался создать макрос для автоматического преобразования типов между двумя типами, где два параметра никогда не должны совпадатьтип.Моя проблема в том, что компилятор выдает ошибку, если я не включаю тот же тип регистра.То, что я хотел:

#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, \

Этот пример является самым простым, который я обнаружил, который потерпит неудачу.

Если я добавлю в "тот же тип"такие строки преобразования:

#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")  \
)

Создает и работает с ожидаемым результатом:

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

, проверяя, что я не использую макрос для расширения каких-либо условий одного типа.Я понимаю, что _Generic не является макросом для подстановки строк, но я также подумал, что, если бы вы могли использовать его без регистра по умолчанию, он корректно выдавал бы ошибку компиляции, если вы использовали неизвестный тип (или неподдерживаемую комбинацию типов, а именно такое поведение я хотелЭто похоже на то, что препроцессор смешивает два макропараметра.

Редактировать: Итак, у меня есть лучшее понимание (см. Мой ответ ниже), но все еще ищу, как макрос выдает ошибку компиляцииесли два параметра одного типа.До сих пор у меня есть хитрость, чтобы заставить ошибку связи, которая все еще лучше, чем ошибка времени выполнения.

Ответы [ 2 ]

6 голосов
/ 19 марта 2019

Проблема в том, что каждая ветвь общего выбора должна быть действительной, даже если они не оцениваются.

Например, ваш первый макрос:

bob(i1, s1)

Расширяется до (типыдобавлено для ясности):

_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")
)

Очевидно, ветвь uint32_t действительна: она просто выбирает "s-l".Но ветвь int16_t не действительна, поскольку from (сам int16_t) не имеет соответствующей ветки.

В этом конкретном сценарии это не повредитдобавить оператор самопреобразования, который ничего не делает.

2 голосов
/ 19 марта 2019

Момент А-Ха, благодаря Джону Боллинджеру в комментариях.

Если я раскрою макрос в коде:

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") ) );

}

Становится очевидным, что это не скомпилируется, если не будут удалены пути, по которым не пройден путь, что, как мне кажется, не то, что происходит.

Итак, я полагаю, что случай по умолчанию - это правильный метод?

Edit: так что я до сих пор не выяснил, как получить регистр по умолчанию, чтобы выдать ошибку компилятора, однако я обнаружил, что если я помещу вызов в случае по умолчанию в несуществующую функцию, она скомпилируется, но сгенерирует компоновщик ошибка, если я нарушаю правило, которое пытаюсь применить. Не отлично, но лучше, чем ошибка времени выполнения.

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")  \
    )

Если у любого, кто читает это, есть лучший, C11, способ встроить _Static_assert в _Generic, дайте мне знать. (Я знаю, что могу встраивать _Generic в _Static_assert, он просто становится очень уродливым, и я не хочу поддерживать дублированную логику)

...