Читая стандарт C++ 17, мне кажется, что существует несоответствие между pp-number, обрабатываемым препроцессором, и числовыми литералами, например user-defined-integer-literal, поскольку они определены для обработки «верхним» языком.
Например, следующее правильно разбирается как pp-number в соответствии с грамматикой препроцессора:
123_e+1
Но в контексте фрагмента кода, совместимого с C++ 11,
int operator"" _e(unsigned long long)
{ return 0; }
int test()
{
return 123_e+1;
}
текущие компиляторы Clang или GCC (я не тестировал другие) вернут ошибку, подобную этой:
unable to find numeric literal operator 'operator""_e+1'
где operator"" _e(...) не найден и попытка определить operator"" _e+1(...) будет недопустимой.
Похоже, это происходит потому, что компилятор сначала преобразует токен как pp-number, но затем не может выполнить откат и применить правила грамматики для user-defined-integer-literal при синтаксическом анализе окончательного выражения.
Для сравнения, следующий код компилируется нормально:
int operator"" _d(unsigned long long)
{ return 0; }
int test()
{
return 0x123_d+1; // doesn't lex as a 'pp-number' because 'sign' can only follow [eEpP]
}
Это правильное прочтение стандарта? И если да, то разумно ли, чтобы компилятор обрабатывал этот, возможно, редкий, угловой случай?
В lex.ppnumber нельзя использовать подчеркивание! Так что разобрать 123_e+1 как pp-number неправильно ...
@Aconcagua - на самом деле лексы pp-numberidentifier-nondigit и nondigit включают подчеркивание.
Это не ответ, потому что я не уверен на 100%, что это верно для C++ 17, но аналогичные угловые случаи существуют в C, и интерпретация такова, что стандарт на самом деле требует это ошибка: каждый pp-токен для преобразования в один и только один токен фазы 7 компилятору не разрешается «откатиться», как вы выразились.
@AndyG О, кажется, вы правы, извините, но + не распространяется? Интересно: p / P включены, но префикса 0x тоже нет ...
Мой первый инстинкт - это максимальный случай жевания, например> = и а +++++ б CC @zwol
@DeiDei MSVC неверен по этому поводу.
@Aconcagua + покрывается продукцией pp-number e sign, что поначалу трудно заметить, а 0x покрывается pp-number identifier-nondigit.
@ShafikYaghmour Хм, это потребует синтаксического анализа 1.e + E + p + P + e как pp-number ... Я бы сказал, что эти правила построения слишком общие, тогда следующий стандарт может (надеюсь) стать более точным (имея два пути: разрешить e sign, только если идентификатор еще не появился, и наоборот) ...
@Aconcagua он будет плохо сформирован на более поздних этапах, грамматика не должна улавливать его здесь.
@ShafikYaghmour Если бы это было так, нам не нужны были бы пробелы, поэтому примеры e и d не нужно было бы рассматривать по-разному. Тем не менее, интересный пример того, как хорошее форматирование может предотвратить ошибки ...
Интересно отметить, что аналогичный 123_p+1 имеет непоследовательную обработку от clang и gcc увидеть это вживую, и использование этого примера в отличие от примера _d и вопрос, почему clang и gcc дают противоречивые результаты, кажется разумным способом отличить это от дубликата.





Вы стали жертвой правило максимального пережевывания, у которого лексический анализатор принимает как можно больше символов, чтобы сформировать действительный токен.
Это описано в разделе [lex.pptoken] p3, в котором говорится (акцент мой):
Otherwise, the next preprocessing token is the longest sequence of characters that could constitute a preprocessing token, even if that would cause further lexical analysis to fail, except that a header-name ([lex.header]) is only formed within a #include directive.
и включает несколько примеров:
[ Example:
#define R "x" const char* s = R"y"; // ill-formed raw string, not "x" "y"— end example ]
4 [ Example: The program fragment 0xe+foo is parsed as a preprocessing number token (one that is not a valid floating or integer literal token), even though a parse as three preprocessing tokens 0xe, +, and foo might produce a valid expression (for example, if foo were a macro defined as 1). Similarly, the program fragment 1E1 is parsed as a preprocessing number (one that is a valid floating literal token), whether or not E is a macro name. — end example ]
5[ Example: The program fragment x+++++y is parsed as x ++ ++ + y, which, if x and y have integral types, violates a constraint on increment operators, even though the parse x ++ + ++ y might yield a correct expression. — end example ]
Это правило действует в нескольких других хорошо известных случаях, таких как а +++++ б и токены> =, что потребовало исправления, чтобы разрешить.
Для справки грамматика pp-токен выглядит следующим образом:
pp-number: digit . digit pp-number digit pp-number identifier-nondigit pp-number ' digit pp-number ' nondigit pp-number e sign pp-number E sign pp-number p sign pp-number P sign pp-number .
Обратите внимание на продукцию e sign, которая и задерживает этот кейс. Если, с другой стороны, вы используете d, как ваш второй пример, вы бы не попали в этот (увидеть это вживую на Godbolt).
Также добавление интервала также решит вашу проблему, поскольку вы больше не будете подвергаться максимальному жеванию (увидеть это вживую на Godbolt):
123_e + 1
С тех пор, как вы связались со мной, я снова много лет не читал внимательно стандарт C++ и, в частности, не знаю, существуют ли особые правила для определяемых пользователем литералов. Эта интерпретация верна для C при условии, что вы также принимаете во внимание обычную интерпретацию 5.1.1.2p1 # 7 «Каждый токен предварительной обработки преобразуется в токен», что означает токен один и только один - компилятор не должен разделять один pp- токен на два токена фазы 7, даже если это приведет к правильному синтаксическому анализу.
(Максимальный munch на токенах фазы 7 будет производить 0xe + foo из примера 4, потому что 0xe является целочисленным литералом, а создание целочисленных литералов не позволяет + в этой точке.)
Спасибо за ответ и за пример из эталона.
@zwol Просто следуя правилам парсера, я думаю, что 0xe+foo нужно будет принять как один токен - насколько я понимаю (и узнал ...), интерпретация токена как целочисленного или литерала FP выполняется на более позднем этапе?
Теперь из любопытства: как покрывается x в 0x? Будет ли он рассматриваться как нецифровой идентификатор при синтаксическом анализе?
@ Аконкагуа, в этом весь вопрос. 0xe+foo - это один токен предварительная обработка. Стандарт мог был написан, чтобы указать, что лексический анализ повторяется после предварительной обработки, и в этом случае pp-токен 0xe+foo станет тремя токенами фазы 7, целочисленным литералом 0x3, оператором + и идентификатором foo. Стандарт C не был написан таким образом; вместо этого он требует, чтобы 0xe+foo был преобразован в токен фазы 7 не замужем, и нет соответствующей лексической продукции, поэтому это синтаксическая ошибка. Я верю, но не на 100% уверен, что стандарт C++ такой же.
@zwol: С ++ то же самое.
@rici Полезно знать, спасибо.
Обратите внимание: MSVC отлично компилирует оба случая.