#c #c-preprocessor #variadic-macros
#c #c-препроцессор #переменные макросы
Вопрос:
Если я попытаюсь скомпилировать следующий код:
template <typename... TArgs>
void Dummy(const TArgs amp;...args)
{
}
#define DUMMY(...) Dummy("Hello", ##__VA_ARGS__)
int main()
{
DUMMY();
}
Я получаю следующую ошибку компиляции:
g -std=c 17 -O3 -Wall main.cpp amp;amp; ./a.out
main.cpp: In function 'int main()':
main.cpp:6:48: error: expected primary-expression before ')' token
6 | #define DUMMY(...) Dummy("Hello", ##__VA_ARGS__)
| ^
main.cpp:10:5: note: in expansion of macro 'DUMMY'
10 | DUMMY();
| ^~~~~
https://coliru.stacked-crooked.com/a/c9217ba86e7d24bd
Код компилируется нормально, когда я добавляю хотя бы один параметр:
template <typename... TArgs>
void Dummy(const TArgs amp;...args)
{
}
#define DUMMY(dummy, ...) Dummy(dummy, ##__VA_ARGS__)
int main()
{
DUMMY(); // This is strange. Why does this compile?
DUMMY(1);
DUMMY(1, 2);
DUMMY(1, 2, 3);
}
https://coliru.stacked-crooked.com/a/e30e14810d70f482
Но я не уверен, что это правильно, потому что DUMMY
принимает хотя бы один параметр, но я передаю ноль.
Комментарии:
1. Я рекомендую вам остановиться после предварительной обработки, чтобы посмотреть, до чего на самом деле расширяются макросы.
2. @Someprogrammerdude,
Dummy("Hello",);
🙁 колиру .
Ответ №1:
Важным фактом о макросах C / C является то, что их невозможно вызвать без параметров, поскольку параметру макроса разрешается быть пустой последовательностью токенов.
Следовательно, DUMMY()
вызывает макрос DUMMY
с одним пустым параметром, а не с нулевыми параметрами. Это объясняет, почему работает второй пример, а также объясняет, почему первый пример выдает синтаксическую ошибку.
Расширение GCC удаляет запятую из, , ##__VA_ARGS__
когда __VA_ARGS__
в нем нет элементов. Но один пустой аргумент — это не то же самое, что отсутствие аргументов. Когда вы определяете DUMMY
как #define DUMMY(...)
, вы гарантируете, что у __VA_ARGS__
есть хотя бы один аргумент, поэтому ,
он не будет удален.
*** Примечание: GCC делает исключение из этого правила, если вы не указываете какой-либо стандарт ISO с --std
опцией. В этом случае, если ...
это единственный параметр макроса и вызов имеет пустой аргумент, то ,##__VA_ARGS__
запятая пропадает. Это указано в руководстве CPP в разделе Variadic Marcos:
Приведенное выше объяснение неоднозначно относится к случаю, когда единственным параметром макроса является параметр переменных аргументов, поскольку бессмысленно пытаться отличить, является ли отсутствие аргумента вообще пустым аргументом или отсутствующим аргументом. CPP сохраняет запятую при соответствии определенному стандарту C. В противном случае запятая удаляется как дополнение к стандарту.
Когда DUMMY
есть #define DUMMY(x, ...)
, __VA_ARGS
будет пустым, если DUMMY
вызывается только с одним аргументом, который включает в себя оба вызова DUMMY()
(один пустой аргумент) и DUMMY(0)
(один аргумент, 0
). Обратите внимание, что стандартный C и C до C 20 не допускали бы этот вызов; они требуют, чтобы был хотя бы один (возможно, пустой) аргумент, соответствующий многоточию. Однако GCC никогда не вводил это ограничение, и GCC опустит запятую с ,##__VA_ARGS__
независимо от --std
настройки.
Начиная с C 20, вы можете использовать __VA_OPT__
встроенный макрос как более стандартный способ работы с запятыми (и любыми другими знаками препинания, которые, возможно, потребуется удалить). __VA_OPT__
также позволяет избежать проблемы, представленной выше, с пустыми аргументами, поскольку он использует другой критерий: __VA_OPT__(x)
расширяется до, x
если __VA_ARGS__
содержит хотя бы один токен; в противном случае он расширяется до пустой последовательности. Следовательно, __VA_OPT__
будет работать так, как ожидалось для макросов в этом вопросе.
Я считаю, что все основные компиляторы сейчас реализуют __VA_OPT__
, по крайней мере, в своих последних версиях.
Комментарии:
1. Почему тогда, когда вы включаете
--std=gnu 17
, gcc рассматриваетDUMMY()
как отсутствие аргументов вместо одного пустого аргумента и удаляет запятую перед##__VA_ARGS__
? Смотрите пример компиляции на coliru .2. @anton_rh: В GCC есть специальное исключение для случая, когда единственный аргумент пуст, а параметр —std указывает gnu. Это в конце документации. Я сделаю пометку в ответе. (Я никогда не замечал этого раньше, потому что я никогда ничего не компилирую с —std=gnu. Извини.)
Ответ №2:
Стандарт __VA_ARGS__
не удаляет конечные ,
значения при использовании нулевых аргументов. Ваш, ##__VA_ARGS__
который удаляет лишнее ,
, является расширением GCC.
Это расширение GCC не работает, потому что вы используете стандартный совместимый режим -std=c 17
вместо -std=gnu 17
.
Комментарии:
1. Хорошо, я немного отредактировал код: здесь . Он все еще использует
-std=c 17
, но отлично компилируется даже с##__VA_ARGS__
.2. @anton_rh Это странно. Интересно, является ли это ошибкой GCC.
3. @user694733: не совсем ошибка. Это ожидаемое (и документированное) поведение для расширения GCC. Смотрите мой ответ.
Ответ №3:
По некоторым причинам (вероятно, это ошибка GCC), если вы используете just #define DUMMY(...)
без других аргументов, то ##__VA_ARGS__
не будет работать должным образом (он не удалит запятую, если __VA_ARGS__
она пуста).
Это верно только при компиляции с -std=c 17
. При компиляции с -std=gnu 17
этого не происходит. Но в любом случае ##__VA_ARGS__
это расширение GCC, и код с ##__VA_ARGS__
вообще не должен компилироваться с -std=c 17
. Но GCC позволяет использовать расширения GCC в -std=c 17
режиме, если вы не установите -pedantic
флаг. Но, похоже, расширения GCC работают по-разному в режиме -std=c 17
и -std=gnu 17
.
Однако проблему можно обойти:
#include <utility>
template <typename... TArgs>
void Dummy(const TArgs amp;...args)
{
}
namespace WA
{
class stub_t {};
stub_t ArgOrStub()
{
return {};
}
template <typename T>
auto ArgOrStub(T amp;amp;t) -> decltype( std::forward<T>(t) )
{
return std::forward<T>(t);
}
template <typename... TArgs>
void RemoveStubAndCallDummy(stub_t, TArgs amp;amp;...args)
{
Dummy(std::forward<TArgs>(args)...);
}
template <typename... TArgs>
void RemoveStubAndCallDummy(TArgs amp;amp;...args)
{
Dummy(std::forward<TArgs>(args)...);
}
}
#define DUMMY(first, ...) WA::RemoveStubAndCallDummy( WA::ArgOrStub(first), ##__VA_ARGS__ )
int main()
{
DUMMY();
}
При вызове DUMMY()
аргумент first
будет пустым, и после предварительной обработки мы получим WA::ArgOrStub()
который вернет stub_t
то, что позже будет удалено при первой перегрузке RemoveStubAndCallDummy
. Это громоздко, но я не смог найти лучшего решения.
Ответ №4:
C 20 представил __VA_OPT__
как способ необязательного расширения токенов в переменном макросе, если число аргументов больше нуля.
Это устраняет необходимость в ##__VA_ARGS__
расширении GCC. Если вы можете использовать эту версию стандарта, это должно стать элегантным решением, не зависящим от компилятора.
Последовательность
__VA_OPT__(x)
, которая допустима только в списке подстановки макроса с переменным аргументом, расширяется до x, если__VA_ARGS__
она непустая, и до nothing, если она пуста.
Таким образом, вы можете просто сделать:
#define DUMMY(...) Dummy("Hello" __VA_OPT__(,) __VA_ARGS__)
Вот хорошая статья в блоге с __VA_OPT__
в действии (и больше о макросах препроцессора):https://www.scs.stanford.edu /~dm/blog/va-opt.html