Вопрос: есть ли возможность обнаружить во время компиляции, если размер макро-аргумента больше, скажем, uint32_t?
Единственный способ сделать это переносимым,генерирует ошибку компилятора с _Generic
.Если вы хотите, чтобы ошибка была красивой и читабельной, вы передаете результат от _Generic
до _Static_assert
, чтобы вы могли напечатать пользовательскую строку в качестве сообщения компилятора.
Ваша спецификация выглядит так:
- Все должно быть проверено во время компиляции.
- Макрос может получить от 1 до 5 параметров любого типа.
- Только
int32_t
и uint32_t
разрешенные типы.
Это означает, что вы должны написать макрос с переменным числом аргументов, и он должен принимать от 1 до 5 параметров.
Такой макрос можно записать так:
#define COUNT_ARGS(...) ( sizeof((uint32_t[]){__VA_ARGS__}) / sizeof(uint32_t) )
#define MY_MACRO(...) \
_Static_assert(COUNT_ARGS(__VA_ARGS__)>0 && COUNT_ARGS(__VA_ARGS__)<=5, \
"MY_MACRO: Wrong number of arguments");
COUNT_ARGS
создает временный составной литерал из столько объектов, сколько вы даете макросу.Если они полностью несовместимы с uint32_t
, вы можете получить здесь ошибки / предупреждения компилятора уже здесь.Если нет, то COUNT_ARGS
вернет количество переданных аргументов.
С этим мы можем выполнить фактическую, переносимую проверку типа каждого элемента в списке переменных аргументов.Чтобы проверить тип одного элемента с помощью _Generic
:
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
Затем передайте результат этого на _Static_assert
.Однако для 5 аргументов нам необходимо проверить от 1 до 5 пунктов.Для этой цели мы можем «связать» несколько макросов:
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
#define CHECK_ARGS1(arg1,...) CHECK(arg1)
#define CHECK_ARGS2(arg2,...) (CHECK(arg2) && CHECK_ARGS1(__VA_ARGS__,0))
#define CHECK_ARGS3(arg3,...) (CHECK(arg3) && CHECK_ARGS2(__VA_ARGS__,0))
#define CHECK_ARGS4(arg4,...) (CHECK(arg4) && CHECK_ARGS3(__VA_ARGS__,0))
#define CHECK_ARGS5(arg5,...) (CHECK(arg5) && CHECK_ARGS4(__VA_ARGS__,0))
Каждый макрос проверяет первый переданный ему аргумент, а затем перенаправляет остальные, если таковые имеются, следующему макросу.Конечный 0 предназначен для закрытия предупреждений ISO C об аргументах rest, необходимых для вариационных макросов.
Мы можем объединить эти вызовы в _Static_assert
, который вызывает соответствующий макрос в «цепочке», соответствующейчисло аргументов:
_Static_assert(COUNT_ARGS(__VA_ARGS__) == 1 ? CHECK_ARGS1(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 2 ? CHECK_ARGS2(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 3 ? CHECK_ARGS3(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 4 ? CHECK_ARGS4(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 5 ? CHECK_ARGS5(__VA_ARGS__,0) : 0, \
"MY_MACRO: incorrect type in parameter list " #__VA_ARGS__); \
Полный код с примерами использования:
#include <stdint.h>
#define COUNT_ARGS(...) ( sizeof((uint32_t[]){__VA_ARGS__}) / sizeof(uint32_t) )
#define CHECK(arg) _Generic((arg), uint32_t: 1, int32_t: 1, default: 0)
#define CHECK_ARGS1(arg1,...) CHECK(arg1)
#define CHECK_ARGS2(arg2,...) (CHECK(arg2) && CHECK_ARGS1(__VA_ARGS__,0))
#define CHECK_ARGS3(arg3,...) (CHECK(arg3) && CHECK_ARGS2(__VA_ARGS__,0))
#define CHECK_ARGS4(arg4,...) (CHECK(arg4) && CHECK_ARGS3(__VA_ARGS__,0))
#define CHECK_ARGS5(arg5,...) (CHECK(arg5) && CHECK_ARGS4(__VA_ARGS__,0))
#define MY_MACRO(...) \
do { \
_Static_assert(COUNT_ARGS(__VA_ARGS__)>0 && COUNT_ARGS(__VA_ARGS__)<=5, \
"MY_MACRO: Wrong number of arguments"); \
_Static_assert(COUNT_ARGS(__VA_ARGS__) == 1 ? CHECK_ARGS1(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 2 ? CHECK_ARGS2(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 3 ? CHECK_ARGS3(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 4 ? CHECK_ARGS4(__VA_ARGS__,0) : \
COUNT_ARGS(__VA_ARGS__) == 5 ? CHECK_ARGS5(__VA_ARGS__,0) : 0, \
"MY_MACRO: incorrect type in parameter list " #__VA_ARGS__); \
} while(0)
int main (void)
{
//MY_MACRO(); // won't compile, "empty initializer braces"
//MY_MACRO(1,2,3,4,5,6); // static assert "MY_MACRO: Wrong number of arguments"
MY_MACRO(1); // OK, all parameters int32_t or uint32_t
MY_MACRO(1,2,3,4,5); // OK, -"-
MY_MACRO(1,(uint32_t)2,3,4,5); // OK, -"-
//MY_MACRO(1,(uint64_t)2,3,4,5); // static assert "MY_MACRO: incorrect type..."
//MY_MACRO(1,(uint8_t)2,3,4,5); // static assert "MY_MACRO: incorrect type..."
}
Это должно быть на 100% переносимо и не зависит от компилятора, дающего дополнительную диагностику за пределамичто требуется стандартом.
Старый трюк do-while(0)
предназначен для обеспечения совместимости со стандартами форматирования фигурных скобок, такими как if(x) MY_MACRO(1) else
.См. Зачем использовать явно бессмысленные операторы do-while и if-else в макросах?