Мъртъв клон на _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