Мертвая ветвь выражения _Generic, вызывающая ошибку компилятора (C11)

Мне было интересно написать макрос проверки типа, который выдает предупреждение только в том случае, если тип не является int/short/long или указателем.

Проблема, с которой я столкнулся, заключается в том, что указатель может быть любым указателем.

#define INT_OR_POINTER_AS_UINTPTR(v) _Generic((v), \
    signed long:  (uintptr_t)(v),            unsigned long:  (uintptr_t)(v), \
    signed int:   (uintptr_t)(v),            unsigned int:   (uintptr_t)(v), \
    signed short: (uintptr_t)(v),            unsigned short: (uintptr_t)(v), \
    default: (((void)(0 ? (*(v)) : 0),                       (uintptr_t)(v))))

Первый блок — разрешить int/short/long

Случай default - разрешить любой указатель.

Намерение (0 ? (*(v)) : 0) состоит в том, чтобы вызвать ошибку компилятора, если v не является указателем, но в противном случае не влияет на сгенерированный код (см. 0 ? ...).

Таким образом, случайные неявные приведения типов из других типов, таких как float или bool, не останутся незамеченными.

В идеале это сработает.

int a = 4;
struct Foo *b = NULL;

uintptr_t test_a = INT_OR_POINTER_AS_UINTPTR(a);
uintptr_t test_b = INT_OR_POINTER_AS_UINTPTR(b);

В идеале, и это потерпит неудачу для обоих применений.

float a = 4;
struct Foo b = {0};

uintptr_t test_a = INT_OR_POINTER_AS_UINTPTR(a);
uintptr_t test_b = INT_OR_POINTER_AS_UINTPTR(b);

Однако, даже когда в качестве аргумента указывается int/long/short, код, который проверяет указатель, оценивается и выдает ошибки: invalid type argument of unary '*' (have 'int')

Без необходимости явно перечислять все типы указателей, которые могут быть переданы этому _Generic, есть ли способ перехватить все типы указателей без оценки выражения для других (не указателей) значений?


person ideasman42    schedule 13.01.2015    source источник
comment
возможный дубликат несовместимых типов указателей, передаваемых в макросе _Generic   -  person J. C. Salomon    schedule 14.01.2015


Ответы (1)


_Bool — это «стандартный целочисленный тип без знака», см. 6.2.5, поэтому, если вам нужна специальная обработка для этого, вам нужно будет сделать это отдельно.

В противном случае есть простой трюк, который вы можете использовать. Целые числа и указатели обладают тем свойством, что симметричная разность является целым числом. Поэтому мы используем симметричную разницу в контексте, который может принимать только целое число, например индексирование массива:

#define INT_OR_POINTER_AS_UINTPTR(v) \
             ((sizeof "Allow only integer and pointer types"[(v)-(v)]), (uintptr_t)(v))

Вычитание не удастся для типов struct, а типы с плавающей запятой дадут разницу с плавающей запятой, которую нельзя использовать для арифметики указателя.

Чтобы отклонить _Bool, вы можете использовать _Generic.

person Ben Voigt    schedule 18.01.2015
comment
Недостатком этого метода является оценка v три раза, а это нет-нет! для макросов. Вы можете упаковать первую часть в sizeof, чтобы избежать этого. - person Jens Gustedt; 18.01.2015
comment
@Jens: отличная идея (исходный макрос также многократно оценивал свой аргумент) - person Ben Voigt; 18.01.2015