Я искал способ проверить, пуст ли список аргументов вариационного макроса. Все решения, которые я нахожу, кажутся либо довольно сложными, либо используют нестандартные расширения.
Я думаю, что нашел простое решение, которое является одновременно компактным и стандартным:
#define is_empty(...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
Вопрос. Существуют ли какие-либо обстоятельства, при которых мое решение не работает или приводит к нечеткому поведению?
Основываясь на C17 6.10.3.2/2 (оператор #): «Литерал строки символов, соответствующий пустому аргументу, равен ""», я считаю, что #__VA_ARGS__ всегда четко определен.
Пояснение к макросу:
"", который состоит только из нулевого терминатора и, следовательно, имеет размер 1.@JohnBollinger Firefox говорит, что ссылка небезопасна. Я читал, что 6.10.3/4 применяется только к макросам, которые не заканчиваются на ...?
GCC возвращает 1 на is_empty(\ ) с дружественным warning: invalid string literal, ignoring final '\'. Я не совсем уверен, что это несоответствующий код и/или проблема с gcc...
@JohnBollinger, у него есть список переменных аргументов, который состоит из одного, но пустого токена. Не уверен, куда вы хотите пойти с этим. Предлагаемый макрос, безусловно, является законным и делает то, что заявлено.
@Lundin, это ссылка на ту же HTML-версию черновика N1570, которую я использовал годами. Очевидно, Firefox перестал доверять ЦС, выдавшему сертификат сервера port70.net (недавно он прекратил поддержку нескольких крупных ЦС). Но в любом случае соответствующая цитата: «в вызове должно быть больше аргументов, чем параметров в определении макроса (исключая ...)».
@JensGustedt, может быть один переменный аргумент, состоящий из нулевых токенов предварительной обработки (A_MACRO(regular_arg,)), но это не то же самое, что пустой список переменных аргументов (A_MACRO(regular_arg)).
«• Во всех остальных случаях он будет иметь размер больше 1». Это правда? is_empty("") ложное срабатывание?
@JohnBollinger Да, и я считаю, что весь абзац посвящен «Если список идентификаторов в определении макроса не заканчивается многоточием ...», или я неправильно читаю?
Ты неправильно читаешь, @Lundin. В параграфе описываются случаи как с вариативным, так и с невариативным числом переменных. Последнее, из которого взята моя цитата, указывает, начиная с «Иначе», и ясно, что оно применяется к вариативному случаю, поскольку оно явно исключает ... из подсчета параметров.
@chux, преобразование в строку токена предварительной обработки "" дает строковый литерал размера 3, содержащий два символа двойных кавычек.
Какой результат вы ожидаете, учитывая #define X (новая строка) #define ONE_ARG(v, ...) (is_empty(__VA_ARGS__)) (новая строка) const int test = ONE_ARG(X, X);?





Лично мне не нравится смешивать оценку на уровне макро/препроцессора и проверку на уровне компиляции.
Кажется, нет стандартного способа сделать это на макроуровне, но здесь существуют хаки: Препроцессор C++ __VA_ARGS__ число аргументов
Я читал о хаках и стандартных расширениях, я просто хочу использовать более простое решение.
@Lundin связанное решение не работает для «без аргументов», то есть никогда не дает 0.
Примечание: эта версия этого ответа является результатом серьезной перезаписи. Некоторые утверждения были удалены, а другие значительно изменены, чтобы сосредоточить внимание на наиболее важных моментах и лучше их обосновать.
[Спорная, много спорная позиция удалена. Это больше отвлекало, чем помогало.]
I think I've found an easy solution that is both compact and standard:
#define is_empty(...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
Мы можем обойти любой вопрос неопределенности, рассмотрев этот вариант:
#define is_empty(dummy, ...) ( sizeof( (char[]){#__VA_ARGS__} ) == 1 )
. К интерпретации пустых аргументов непустой переменной против. здесь применимы те же соображения, что и в исходной версии. Конкретно,
Based on C17 6.10.3.2/2 (the # operator): "The character string literal corresponding to an empty argument is """, I believe that
#__VA_ARGS__is always well-defined.
Я согласен. Здесь также актуален раздел 6.10.3.1/2: «Идентификатор __VA_ARGS__, встречающийся в списке замены, должен рассматриваться как параметр [...]».
Explanation of the macro:
- This creates a compound literal char array and initializes it by using a string literal.
да.
- No matter what is passed to the macro, all arguments will be translated to one long string literal.
да. __VA_ARGS__ рассматривается как параметр а (один). Если есть несколько переменных аргументов, это может повлиять на повторное сканирование, но оператор строкового преобразования действует в точке раскрытия макроса перед повторным сканированием.
- In case the macro list is empty, the string literal will become "", which consists only of a null terminator and therefore has size 1.
да.
- In all other cases, it will have a size greater than 1.
да. Это справедливо даже в случае двух аргументов с нулевым токеном в списке переменных аргументов, is_empty(dummy,,), где #__VA_ARGS__ будет расширяться до ",". Это также верно в случае аргумента, состоящего из пустого строкового литерала, is_empty(dummy, ""), где #__VA_ARGS__ расширяется до "\"\"".
ОДНАКО, это все еще может не служить вашей цели. В частности, вы не можете использовать его в директиве условной компиляции. Хотя выражения sizeofв общем разрешены в целочисленных константных выражениях, например, формируют управляющие выражения таких директив,
sizeof классифицируется как идентификатор (нет различия между ключевыми словами и идентификаторами для токенов предварительной обработки), исогласно пункт 6.10.1/4 стандарта, при обработке управляющего выражения директивы условной компиляции,
After all replacements due to macro expansion and the defined unary operator have been performed, all remaining identifiers (including those lexically identical to keywords) are replaced with the pp-number 0
(выделение добавлено).
Следовательно, если ваш макрос используется как или в управляющем выражении директивы условной компиляции, он будет оцениваться так, как если бы оператор sizeof в нем был заменен на 0, что привело к недопустимому выражению.
«Это языковое ограничение, поэтому C не только не определяет поведение программы, содержащей вызов вариативного макроса с пустым списком VA, но и соответствующая реализация выдаст об этом диагностику». Но он чисто компилируется с последними clang, gcc и icc. godbolt.org/z/CBVm9s. Может быть, это недостаток стандарта, разве не должно быть сказано, что «аргументов должно быть как минимум столько же»? Потому что, очевидно, нам разрешено вызывать printf("test"), 1 аргумент, даже несмотря на то, что это функция int printf(fmt, ...), а макросы с переменным числом символов предназначены для имитации функций с переменным числом аргументов.
@Lundin, правила для вариативных функций отличаются от правил для вариативных макросов. Для функций требуется по крайней мере один именованный параметр и разрешены пустые списки аргументов с переменным числом аргументов. Макросы не требуют именованных параметров и не допускают пустых списков аргументов.
@Lundin, компилятор, который не диагностирует вызов вариативного макроса с пустым списком аргументов переменной, является несоответствующим по этой причине. Я не нахожу особенно удивительным, что различные компиляторы не соответствуют требованиям по умолчанию, и я также не нахожу это особенно важным. Я не знаю другого способа интерпретировать ваш запрос на способ действительный для диагностики пустого списка виртуальных машин, кроме как запрос на соответствующий.
Я также отмечаю, что для GCC, по крайней мере, если я передам аргумент -pedantic, чтобы гарантировать, что он выдает все диагностические данные, требуемые стандартом, тогда он действительно диагностирует вызов вариационного макроса с пустым списком переменных аргументов (в однозначном случае, CALL(foo)против.CALL(foo,)).
"неоднозначность в языке" Я в замешательстве. двусмысленность подразумевает две интерпретации; стандарт не указывает подразумевает, что мы не можем выбрать один. Но я вижу только одну интерпретацию is_empty( ) данной #define is_empty(...), недвусмысленно указанную самим абзацем, который вы цитируете... то есть is_empty( ) в данном случае является вызовом макроса с одним аргументом.
@HWalters, прежде чем вы сможете использовать аргументы комбинировать (хотя бы один требуется) для формирования переменных аргументов, согласно цитате, вы должны в первую очередь использовать аргументы имеют. Это то, что неоднозначно в случае списка аргументов с нулевым токеном. Если бы is_empty вместо этого был невариативным макросом, подобным функции, принимающим нулевые аргументы (#define is_empty()), то вы бы интерпретировали вызов формы is_empty() — точно такой же последовательности токенов — как (ошибочно) наличие аргумента?
@Lundin Ошибка в стандарте не определяет поведение. Стандарт — это то, с чем работают разработчики, поэтому, если нет отчета о проблеме, касающегося этого конкретного аспекта стандарта — и он учитывается в выбранном вами компиляторе (!), вы должны воздерживаться от написания такого кода, если только ваш поставщик компилятора задокументировали поведение, которое соответствует вашим потребностям, но тогда ваша программа больше не является переносимой.
Я думаю, что вы просто ошибаетесь в своей интерпретации. Макрос, содержащий только ... в качестве списка аргументов, который вызывается без ничего между (), вызывается с одним аргументом, пустым токеном. Так что у него действительно на один аргумент больше, чем у макроса без .... Я не вижу здесь никакой двусмысленности, и все известные мне компиляторы интерпретируют стандарт именно так. И тогда вы также, кажется, ошибаетесь в своем заключении, если бы оно было неопределенным, использование его таким образом не было бы нарушением ограничений. Это будет зависеть от реализации, разрешить это или нет.
Что ж, @JensGustedt, у тебя такое же право на свое мнение, как и у меня на свое. Но хотя я хорошо понимаю, как стандарт допускает интерпретацию, которую вы описываете, я искренне не понимаю, как вы или кто-либо другой можете сделать вывод, что это единственная жизнеспособная или правдоподобная интерпретация. Я даже не уверен, как вы пришли к выводу, что наблюдаемое поведение реализаций поддерживает исключительно эту интерпретацию, в отличие, скажем, от реализаций, просто игнорирующих правило C о наличии хотя бы одного переменного аргумента.
@JohnBollinger Учитывая #define Z(), Z() — это вызов макроса с 0 аргументами. OTOH, учитывая #define O(x), O() является вызовом макроса с 1 аргументом (сравните определение/использование q в 6.10.3.5ex3). Оба вызова имеют нулевые токены в скобках, но этого недостаточно для подсчета; количество аргументов в соответствии с 6.10.3p4 исходит из того, как определен макрос, и Z определяется без параметров, тогда как O определяется с одним. Итак, учитывая #define V(...), V() согласно 6.10.3p4 имеет один аргумент (на единицу больше нуля), например O(). Я не вижу фразы, предполагающей, что у нее 0 аргументов, таких как Z().
Кроме того, вы, кажется, запутались в препроцессоре. sizeof — ключевое слово, а ключевые слова — жетоны (6.4p1, 6.4.1p2). Токены — это минимальные лексические элементы на этапах перевода 7 и 8; фазы с 3 по 6 используют предварительная обработка токенов (6.4p3). Директивы предварительной обработки удаляются в конце фазы трансляции 4 (5.1.1.2p1). Итак, очень действительно ясно, что sizeof не работает в условной директиве; это три этапа перевода, прежде чем даже стать ключевым словом.
@HWalters, вы утверждаете, что интерпретация количества аргументов для вызова макроса зависит от определения макроса. Это нет (явно), указанный стандартом. Стандарт, как правило, уделяет большое внимание подробностям такого рода. Единственная причина даже рассматривать несколько надуманную интерпретацию одного аргумента с нулевым токеном - это ожидание в некоторых кругах - явно не поддерживаемое стандартом - что один должен сможет использовать пустой список аргументов с вариативным макросом.
@HWalters, некоторые из тех же людей ожидают, что смогут опустить все переменные аргументы для переменных макросов, имеющих хотя бы один именованный аргумент #define foo(x, ...), и хотя большинство компиляторов примут это, это явно противоречит стандарту. Это устанавливает, что ожидания людей не являются надежной основой для интерпретации стандарта.
@HWalters, что касается sizeof, это оператор, а не ключевое слово (см. раздел 6.5.3 и сравните с 6.4.1). Это явно разрешено в грамматике для формального constant-expression производства стандарта, и именно так оно также определяет выражение в директиве условной компиляции. Тогда объясните мне немного подробнее, почему sizeof не должен распознаваться в директивах условной компиляции, а другие операторы, такие как +, распознаются?
@JohnBollinger То, что sizeof является оператором, не означает, что sizeof не является ключевым словом. Ключевые слова — это лексические единицы; это просто зарезервированные идентификаторы. Операторы — это части выражений. См. контекст 6.5p1 и 6.10.1p1 как для его контекста, так и для его применимости к целочисленным константным выражениям, поддерживаемым условными выражениями. См., в частности, сноску: «Поскольку управляющее константное выражение оценивается на этапе трансляции 4, все идентификаторы либо являются, либо не являются именами макросов — просто нет ключевых слов, констант перечисления и т. д.».
«Это не (явно) указано в стандарте». Для меня ясно, что Z() имеет 0 аргументов; что O() разрешено; и что, учитывая, что O() разрешено, O() имеет 1 аргумент. Как вы думаете, какие из этих вещей стандарт явно не поддерживает?
@HWalters, на самом деле, то, что sizeof является оператором, делает означает, что это не ключевое слово. И то, что его нет в списке ключевых слов (раздел 6.4.1), подтверждает это. Более того, то, что он имеет лексическую форму идентификатора, не означает, что он не может или не должен интерпретироваться препроцессором как оператор, поскольку препроцессор определенно делает распознает операторы defined и _Pragma, оба из которых имеют лексическую форму идентификаторов .
@HWalters, если вызов макроса O() разрешен, то это потому, что следует из нулевые токены предварительной обработки его списка аргументов интерпретируются как один аргумент вместо нуля. Использование разрешенного O() в качестве оправдания такой интерпретации списка аргументов делает вашу логику замкнутой. Естественное прочтение стандарта привело бы меня к выводу, что и O(), и Z() имеют нулевые аргументы, и поэтому O() не допускается. Я не вижу ничего в стандарте, чтобы противоречить этому. Тем не менее, некоторые делать интерпретируют его по-разному, поэтому он явно неоднозначен.
Давайте продолжить обсуждение в чате.
Вы упускаете из виду тот факт, что ни один соответствующий вызов вариационного макроса не имеет пустого списка переменных аргументов? Не может быть подходящего способа проверить это условие (кроме предположения, что оно не выполняется), потому что любой код, который демонстрирует это, имеет неопределенное поведение по этой причине.