C11 _ Общее использование

#c #generics #macros #default #c11

#c #дженерики #макросы #По умолчанию #c11

Вопрос:

Я пытался узнать, как использовать «новые» общие выражения C11, но я столкнулся со стеной.

Рассмотрим следующий код:

 #include <stdlib.h>
#include <stdio.h>

#define test(X, Y, c) 
    _Generic((X), 
        double:     _Generic((Y), 
                        double * :  test_double, 
                        default: test_double 
                    ), 
        int:        _Generic((Y), 
                        int * : test_int, 
                        default: test_int 
                    ) 
    ) (X, Y, c)


int test_double(double a, double *b, int c);
int test_int(int a, int *b, int c);

int test_double(double a, double *b, int c) { return 1; }
int test_int(int a, int *b, int c) { return 2; }

int main()
{
    double *t = malloc(sizeof(double));
    int *s = malloc(sizeof(int));
    int a1 = test(3.4, t, 1);
    int i = 3;
    int a2 = test(i, s, 1);
    printf("%dt", a1);
    printf("%dn", a2);
    return 0;
 }
  

Все это отлично работает, но я все же не понимаю, почему эти случаи по умолчанию в «_Generic ((Y), …» необходимы, в то время как я могу опустить его в конце «_Generic ((X), …» без последствий.

Фактически, если я удалю эти два значения по умолчанию, я получу сообщение об ошибке (gcc 5.4.0), в котором говорится, что «селектор типа ‘double *’ несовместим ни с какой ассоциацией» при расширении макросов » int a1 = test (3.4, t, 1);» и то же самое с «int *»при тестировании макрорасширения (i, s, 1)

Действительно ли «по умолчанию» необходимо или я что-то упускаю? В первом случае, какого черта это должно быть? Если у меня есть только test_double и test_int, которые можно вызвать, почему я должен указывать значение по умолчанию для чего-то, что никогда не должно даже компилироваться?

Комментарии:

1. Ваше описание сбивает с толку. Если вы удалите default в Generic(Y, вы получите ошибку?

2. Да, именно. Тот, который выделен жирным шрифтом.

Ответ №1:

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

Более простой случай:

 int main(void)
{
    int x;

    _Generic(0, int: x = 5, float: x = (void)0);
}
  

Этот код приводит к нарушению ограничений в gcc, поскольку он выполняет проверку ограничений для всех связанных выражений (а не только для выбранного) и x = (void)0 содержит нарушение ограничений.


Применяя этот принцип к вашему коду без регистра по умолчанию, мы видим, что проблема заключается в том, что когда экземпляр макроса создается с Y помощью переменной as , объявленной как int *s , тогда одно из связанных выражений является _Generic(s, double * : test_double) , что является нарушением ограничений, поскольку регистр не сопоставляется.

Комментарии:

1. Знаете ли вы, есть ли отчет о дефекте / попытка улучшить ситуацию? Я ничего не смог найти, поэтому сейчас я пытаюсь выполнить необходимую работу.

2. @DanielJour не уверен, извините. Вот список всех предложений, над которыми в настоящее время ведется работа для C2X

Ответ №2:

TL; DR

Выбор происходит во время компиляции, но это не означает, что другой (не выбранный) код отбрасывается. Он все еще должен быть действительным, и это означает, что …

Если значение по умолчанию не используется и ни одно из имен типов не совместимо с типом управляющего выражения, программа не будет компилироваться.

(Источник)


Это было удивительно:

Без регистра по умолчанию для «первого Y»:

 #define test(X, Y, c) 
    _Generic((X), 
        double:     _Generic((Y), 
                        double * :  test_double 
                    ), 
        int:        _Generic((Y), 
                        int * : test_int, 
                        default: test_default 
                    ) 
    ) (X, Y, c)
  

Я получаю эту ошибку:

прога.c: 6:30: ошибка: селектор ‘_Generic’ типа ‘int *’ несовместим ни с какой ассоциацией

Обратите внимание, что он жалуется int * на то, что он несовместим! Почему? Что ж, давайте посмотрим на строку отчета:

     int a2 = test(i, s, 1);
  

X имеет тип int и Y тип int * .

Теперь наступает важная часть: расширения происходят безоговорочно. Таким образом, несмотря X на то, что имеет тип int , первая ассоциация для X (когда она имеет тип double ) должна быть хорошо сформированной программой. Таким образом, при Y int * том, что должно быть правильно сформировано следующее:

 _Generic((Y), 
             double * :  test_double 
              ), 
  

И поскольку an int * не является a double * , здесь все ломается.


Я просто просматривал стандарт (на самом деле N1570) и не смог найти ничего, что на самом деле точно определяет это поведение. Я думаю, в этом случае можно было бы сообщить о дефекте, стандарт слишком расплывчат в этом отношении. Я пытаюсь сделать это сейчас.

Комментарии:

1. Привет, ты отправил отчет в конце? Если да, то как все прошло?

Ответ №3:

Очевидно, это происходит из-за вложенных _Generic выборок.

Проблема в том, что с _Generic совместимость типов должна быть удовлетворена во время компиляции для всех возможных общих ассоциаций. Даже если выбрана только одна ассоциация, те, которые не выбраны, все равно должны иметь некоторую совместимую ассоциацию.

Ассоциация по умолчанию обрабатывает каждую ассоциацию, которая несовместима с остальными ассоциациями для этого _Generic выбора.

Допустим, вы удаляете ассоциацию по умолчанию int: _Generic((Y), . . Если вы это сделаете, то будет выбрана двойная ассоциация, но ассоциация int все равно должна обрабатывать тип double *, что обычно выполняется по умолчанию. В этом случае существует только ассоциация int *, и она выдает ошибку.

Ответ №4:

Это порядок приоритета. В вашем коде эти два _Generic((Y) выполняются раньше _Generic((X) . Они заключены в круглые скобки. Компилятор готов и работает как для test(3.4, t, 1) , так и test(i, s, 1) для .

Я бы немного переписал код.

 #define test(X, Y, c) 
    _Generic((X), 
        double:     _Generic((Y), 
                    double * :  test_double, 
                    default : test_error 
                ), 
        int:        _Generic((Y), 
                    int * : test_int, 
                    default : test_error 
                ) 
    ) (X, Y, c)

static void test_error(void)
{
    /* This function should be optimised away by compiler. */
    assert(0);
}
  

Комментарии:

1. Приоритет и скобки здесь не имеют значения

2. @M.M Мое изменение проверяет два аргумента и должно выдать хорошую ошибку для неподдерживаемой пары. Рассматриваемый код проверяет только первый.