Определение, является ли аргумент макроса типизированным именем - PullRequest
10 голосов
/ 15 мая 2019

В C11 / gnuC11 возможно написать макрос, который возвращает целочисленное константное выражение со значением 1 или 0 соответственно, если аргумент макроса является или не является именем типа, или, по крайней мере, макрос может различать целочисленные константные выражения иtypenames (то есть, если может обнаружить аргумент не один из них, он может предположить, что это другой)?

#define IS_TYPENAME(X) /*???*/ 
_Static_assert( IS_TYPENAME(int), "" );
_Static_assert( !IS_TYPENAME(42), "" );

Мотивация:

Моя мотивация заключалась в переносе _Alignaс макросом, который просто ничего не будет делать, если предложенное выравнивание (либо тип, либо целочисленное выражение) будет меньше текущего (обычное _Alignas с меньшим выравниванием, что приводит к ошибке), и поэтому я хотел также принять либо имя типаили целочисленный expr, но теперь я думаю, что просто запрос целочисленного expr (который вы всегда можете получить из typename, применяя _Alignof) будет более простым / понятным способом.

1 Ответ

4 голосов
/ 17 мая 2019

Для этого вам нужно проверить, имеет ли параметр тип integer, и вам нужно проверить, является ли он типом или выражением.


Проверка, является ли параметр макроса, который может быть типом или выражением, целочисленного типа:

Это можно сделать с помощью _Generic. Выражение _Generic не может содержать два идентичных типа, поэтому его будет достаточно, если вы сравните только со всеми типами stdint.h. Так как они будут псевдонимами с целочисленными типами по умолчанию, но не будут конфликтовать друг с другом (как, например, int и long могли бы).

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

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

Дано (x)+0.

  • Если x является типом, () становится оператором приведения, а +0 является унарным оператором сложения, применяемым к целочисленной константе.
  • Если x является выражением, оно будет заключено в скобки, а затем + является оператором двоичного сложения.

Так что вы можете сделать:

#define IS_INT(x) _Generic((x)+0, \
  uint8_t:  1, int8_t:  1,        \
  uint16_t: 1, int16_t: 1,        \
  uint32_t: 1, int32_t: 1,        \
  uint64_t: 1, int64_t: 1,        \
  default: 0)

Это будет работать для всех целочисленных, символьных и плавающих типов, а также для указателей. Он не будет работать с типами struct / union (ошибка компилятора). Он не будет работать с void* и, вероятно, не с NULL (ошибка компилятора, невозможно выполнить арифметику указателей).


Проверка, является ли макрос-параметр, который может быть типом или выражением, выражением:

Это также можно сделать, используя тот же трюк, что и выше, используйте неоднозначность между различными операторами. Например:

#define IS_EXPR(x) (!!(x) + !(x) + 1 == 2)
  • Если x является ненулевым целочисленным константным выражением, мы получаем 1 + 0 + 1 = 2.
  • Если x является константным выражением с нулевым целым числом, мы получаем 0 + 1 + 1 = 2.
  • Если x является типом, мы получаем !!(int)+!(int)+1, что равно 0. Оба + одинарные.

Это не делает разницы между числами с плавающей точкой и целыми числами, поэтому нам нужно объединить этот трюк с макросом IS_INT.


Решение:

#define IS_INTCONSTEXPR(x) ( IS_INT(x) && IS_EXPR(x) )

Завершите пример тестовыми случаями, напечатав 1, если целочисленное константное выражение, в противном случае 0:

#include <stdint.h>
#include <stdio.h>

#define IS_INT(x) _Generic((x)+0, \
  uint8_t:  1, int8_t:  1,        \
  uint16_t: 1, int16_t: 1,        \
  uint32_t: 1, int32_t: 1,        \
  uint64_t: 1, int64_t: 1,        \
  default: 0)

#define IS_EXPR(x) (!!(x) + !(x) + 1 == 2)

#define IS_INTCONSTEXPR(x) ( IS_INT(x) && IS_EXPR(x) )


#define test(arg) printf("%d %s\n", IS_INTCONSTEXPR(arg),(#arg))

int main (void)
{
  test(42);
  test(sizeof(int));
  test(1+1);
  test(int);
  test(unsigned int);
  test(42.0);
  test(double);
  test(uint32_t);
  test(uint32_t*);
  test(_Bool);

  _Static_assert( !IS_INTCONSTEXPR(int), "" ); // OK, passed
  _Static_assert( IS_INTCONSTEXPR(42), "" );   // OK, passed

  return 0;
}

Выход:

1 42
1 sizeof(int)
1 1+1
0 int
0 unsigned int
0 42.0
0 double
0 uint32_t
0 uint32_t*
0 _Bool
...