Разрешены ли пустые макросы стандартом языка C++? - программирование

Разрешены ли пустые макросы стандартом языка C++?

Согласно N3092 (окончательный проект комитета С++ 11), в 16.3: Замена макроса, объектоподобный макрос определяется как:

# define identifier replacement-list new-line

и подобный функции макрос определяется как:

# define identifier lparen identifier-list_opt ) replacement-list new-line
# define identifier lparen ... ) replacement-list new-line
# define identifier lparen identifier-list , ... ) replacement-list new-line

У всех есть список замены (так называемое значение макроса), и это определяется как:

replacement-list:
     pp-tokensopt

и pp-токены определяются как:

pp-tokens:
    preprocessing-token
    pp-tokens preprocessing-token

и токен предварительной обработки является, например, идентификатором.

Насколько я прочитал, макрос с пустым значением четко определен.
Тем не менее, 16.3-3 говорит:

Между идентификатором и списком замещения в определении объектоподобного макроса должен быть пробел.

и обратите внимание, что список замен сам по себе не является обязательным (хотя он может иметь пустое значение).

Поэтому я верю:

//well-formed (function-like macro with value)
#define f(x) (x)<NEWLINE>

//well-formed (function-like macro without value)
#define f(x)<NEWLINE>

//well-formed (function-like macro without value)
#define f(x) <NEWLINE>

//well-formed (object-like macro with value)
#define f hello<NEWLINE>

//**ill-formed** (object-like macro without value)
#define f<NEWLINE>

//well-formed (object-like macro without value)
#define f <NEWLINE>

и, таким образом, например, так называемая "защита включения" должна иметь вид:

#ifndef is_xyz_included<NEWLINE>
    #define is_xyz_included <NEWLINE> // NOTE THE SPACE BEFORE <NEWLINE>
#endif<NEWLINE>

Моя интерпретация неверна?

4b9b3361

Ответ 1

Я считаю, что ваша интерпретация верна. Следовательно, действительный включающий охранник должен иметь форму, которую вы описываете, с пробелом, который не является новой строкой. Не перевод строки из-за

[CPP]

4 Единственными пробельными символами, которые должны появляться между токенами предварительной обработки в директиве предварительной обработки (сразу после введения # токена предварительной обработки непосредственно перед завершающим символом новой строки), являются пробел и горизонтальная табуляция (включая пробелы, которые заменили комментарии или возможно, другие символы пробела в фазе перевода 3).

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

Но учитывая, насколько распространена эта идиома с включенными защитными элементами, настолько, что сам стандарт содержит такой пример, это, вероятно, дефект формулировки, основанный на предположении, что список замены не должен быть пустым, чтобы макрос мог иметь какой-либо использовать.

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

Ответ 2

Моя интерпретация неверна?

Я считаю, что ваша интерпретация верна. GCC и Clang испускают диагностику для плохо сформированного макроопределения.

//**ill-formed** (object-like macro without value)
#define f<NEWLINE>

GCC:

#1 with x86-64 gcc 9.1
<source>:2:9: warning: ISO C++11 requires whitespace after the macro name
    2 | #define f<NEWLINE>

Clang:

<source>:2:10: warning: ISO C99 requires whitespace after the macro name [-Wc99-extensions]    
#define f<NEWLINE>

Посмотреть демо

Ответ 3

Стандарт гласит:

Между идентификатором и списком замещения в определении объектоподобного макроса должен быть пробел.

Таким образом, для:

#define foo

просто после новой строки после foo нам нужно проверить требования стандарта.

Для того, чтобы между идентификатором и списком замещения был пробел, после foo должен быть пробел. Есть; символ новой строки считается пробелом, и в стандарте не указано, что это должен быть какой-то определенный тип пробела (например, обычный пробел или табуляция). Поскольку после foo есть пробел, это означает требование пробел между идентификатором и списком замены уже выполнен. Так что пока все действительно.

Затем нам нужно проверить, есть ли список замены. Нет, так как символ новой строки отмечает конец определения макроса. Таким образом, у нас есть пустой список замены. Действителен до сих пор.

Определение макроса закончено, и все до этого момента является действительным. Таким образом, макроопределение не является плохо сформированным.

Тот факт, что символ новой строки здесь служит двум целям (пробел между идентификатором и списком замещения, а также терминатор определения макроса), не делает макрос неправильным.

Ответ 4

Моя интерпретация неверна?

Да. Ниже приведена совершенно корректная программа C++. Пробела после A в строке нет.

#define A

int main() {}

Из вашего поста replacement_list pp-tokens список состоит из необязательных pp-tokens, что делает пустой список replacement_list вполне допустимым для использования, когда #define -ing объектно-подобный макрос.

Стандарт даже включает в себя пример пустого макроса, который не имеет пробела после макроса. См. Https://timsong-cpp.github.io/cppwp/n3337/cpp#6.

#define EMPTY
EMPTY   #   include <file.h>

Обратите внимание, что вторая строка недопустима, но первая строка верна.