Как я могу гарантировать типобезопасность аргументов с переменным числом аргументов?

В C я хотел бы сделать функцию или макрос, который выглядит так:

void Log(char* what, ...) 

где ... должны быть парами ключ-значение const char*. Мне бы очень хотелось, чтобы код, который не следует этому, взорвался во время компиляции. Я пытался найти директиву __attribute((..)) для этого, но безрезультатно. Любые идеи?


person Mark Pauley    schedule 22.01.2014    source источник
comment
Ты не можешь, извини. Кроме того, не забывайте, что вам понадобится способ сигнализировать о том, что пар значений больше нет (например, с нулевым сигнальным значением).   -  person zneak    schedule 23.01.2014
comment
Вместо этого лучше передать массив или два массива, нет смысла использовать функции с переменным числом аргументов, если вы с самого начала знаете, какие типы вам нужны для ваших аргументов.   -  person Crowman    schedule 23.01.2014


Ответы (3)


где ... должны быть парами ключ-значение const char*

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

Просто передайте массив.

void Log(const char *what, const char **args, size_t size);
person Ed S.    schedule 22.01.2014
comment
Интересно, я действительно был настроен использовать здесь функцию журнала с переменным числом переменных. Мне очень нравится ваша точка зрения, и, возможно, вы только что решили для меня другую проблему (а именно, как вести журнал в стиле print-f с метаданными ключ-значение). - person Mark Pauley; 23.01.2014
comment
@MarkPauley: Для этого загляните в обертки, которые принимают valist. Все функции, которые принимают аргументы с переменным числом аргументов, должны их предоставлять. - person Ed S.; 23.01.2014

C не имеет возможности обеспечить безопасность типов для произвольных аргументов с переменным числом аргументов. GCC и Clang имеют __attribute__ для некоторых фиксированных случаев (когда вариационная функция ожидает, что последним аргументом будет NULL, вы можете использовать __sentinel__; или когда это строка формата, вы можете использовать format), но универсального решения не существует.

С другой стороны, в С++ 11 есть вариативные шаблоны для решения этой проблемы, но, поскольку вы упомянули, что работаете на C, это не сработает для вас.

person zneak    schedule 22.01.2014
comment
Да, просто собираюсь использовать массив с нулевым завершением. Я уже использую атрибут формата для другой версии этого, но меня попросили добавить пары ключ-значение для метаданных журнала. - person Mark Pauley; 23.01.2014

Хорошо, я нашел обходной путь:

#define ASSERT_LIST_TYPE(t, l...) do{t _t[] = {l};}while(0)

void _Log(char* what, ...);

#define Log(what, args...) do{\
  ASSERT_LIST_TYPE(char*, args);\
 _Log(what, args);\
}while(0)

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

Я планирую #ifdef вывести ASSERT_LIST_TYPE, если это не отладочная сборка, на всякий случай.

** Редактировать **

Основываясь на отзывах здесь, я изменил код, чтобы он выглядел так:

void _Log(char* what, const char** args, size_t args_len);

#define Log(what, args...) do{\
  const char* a[] = {args};\
  _Log(what, a, (sizeof a)/sizeof(char*));
}while(0)

Кажется, это работает, даже если args пуст, и он ловит кого-то, передающего литеральную строку obj-c вместо (@"..." вместо "..."), что и укусило меня раньше.

person Mark Pauley    schedule 23.01.2014
comment
Будьте осторожны с этим: он будет дважды оценивать ваши аргументы, один раз для инициализации массива и один раз, когда вы передаете их функции. Если вы передаете только локальные переменные и выражения без побочных эффектов, все в порядке, но если вы прямо передаете вызов функции, у вас могут возникнуть неприятные проблемы, которые будет труднее диагностировать, чем использование неправильного типа в вызове функции с переменным числом аргументов. - person zneak; 23.01.2014