Несогласованность анализа числовых литералов в соответствии с грамматикой стандарта C++

Читая стандарт 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]
    }

Это правильное прочтение стандарта? И если да, то разумно ли, чтобы компилятор обрабатывал этот, возможно, редкий, угловой случай?

Обратите внимание: MSVC отлично компилирует оба случая.

DeiDei 11.12.2018 12:43

В lex.ppnumber нельзя использовать подчеркивание! Так что разобрать 123_e+1 как pp-number неправильно ...

Aconcagua 11.12.2018 13:17

@Aconcagua - на самом деле лексы pp-numberidentifier-nondigit и nondigit включают подчеркивание.

Andy G 11.12.2018 13:21

Это не ответ, потому что я не уверен на 100%, что это верно для C++ 17, но аналогичные угловые случаи существуют в C, и интерпретация такова, что стандарт на самом деле требует это ошибка: каждый pp-токен для преобразования в один и только один токен фазы 7 компилятору не разрешается «откатиться», как вы выразились.

zwol 11.12.2018 13:25

@AndyG О, кажется, вы правы, извините, но + не распространяется? Интересно: p / P включены, но префикса 0x тоже нет ...

Aconcagua 11.12.2018 13:28

Мой первый инстинкт - это максимальный случай жевания, например> = и а +++++ б CC @zwol

Shafik Yaghmour 11.12.2018 14:55

@DeiDei MSVC неверен по этому поводу.

Shafik Yaghmour 11.12.2018 18:56

@Aconcagua + покрывается продукцией pp-number e sign, что поначалу трудно заметить, а 0x покрывается pp-number identifier-nondigit.

Shafik Yaghmour 11.12.2018 19:01

@ShafikYaghmour Хм, это потребует синтаксического анализа 1.e + E + p + P + e как pp-number ... Я бы сказал, что эти правила построения слишком общие, тогда следующий стандарт может (надеюсь) стать более точным (имея два пути: разрешить e sign, только если идентификатор еще не появился, и наоборот) ...

Aconcagua 11.12.2018 19:09

@Aconcagua он будет плохо сформирован на более поздних этапах, грамматика не должна улавливать его здесь.

Shafik Yaghmour 11.12.2018 19:12

@ShafikYaghmour Если бы это было так, нам не нужны были бы пробелы, поэтому примеры e и d не нужно было бы рассматривать по-разному. Тем не менее, интересный пример того, как хорошее форматирование может предотвратить ошибки ...

Aconcagua 11.12.2018 19:15

Интересно отметить, что аналогичный 123_p+1 имеет непоследовательную обработку от clang и gcc увидеть это вживую, и использование этого примера в отличие от примера _d и вопрос, почему clang и gcc дают противоречивые результаты, кажется разумным способом отличить это от дубликата.

Shafik Yaghmour 12.12.2018 19:26
Стоит ли изучать PHP в 2026-2027 годах?
Стоит ли изучать PHP в 2026-2027 годах?
Привет всем, сегодня я хочу высказать свои соображения по поводу вопроса, который я уже много раз получал в своем сообществе: "Стоит ли изучать PHP в...
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Приемы CSS-макетирования - floats и Flexbox
Приемы CSS-макетирования - floats и Flexbox
Здравствуйте, друзья-студенты! Готовы совершенствовать свои навыки веб-дизайна? Сегодня в нашем путешествии мы рассмотрим приемы CSS-верстки - в...
Тестирование функциональных ngrx-эффектов в Angular 16 с помощью Jest
В системе управления состояниями ngrx, совместимой с Angular 16, появились функциональные эффекты. Это здорово и делает код определенно легче для...
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
12
12
326
1
Перейти к ответу Данный вопрос помечен как решенный

Ответы 1

Ответ принят как подходящий

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

Это описано в разделе [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, даже если это приведет к правильному синтаксическому анализу.

zwol 11.12.2018 15:35

(Максимальный munch на токенах фазы 7 будет производить 0xe + foo из примера 4, потому что 0xe является целочисленным литералом, а создание целочисленных литералов не позволяет + в этой точке.)

zwol 11.12.2018 15:36

Спасибо за ответ и за пример из эталона.

Andy G 11.12.2018 17:30

@zwol Просто следуя правилам парсера, я думаю, что 0xe+foo нужно будет принять как один токен - насколько я понимаю (и узнал ...), интерпретация токена как целочисленного или литерала FP выполняется на более позднем этапе?

Aconcagua 11.12.2018 19:18

Теперь из любопытства: как покрывается x в 0x? Будет ли он рассматриваться как нецифровой идентификатор при синтаксическом анализе?

Aconcagua 11.12.2018 19:19

@ Аконкагуа, в этом весь вопрос. 0xe+foo - это один токен предварительная обработка. Стандарт мог был написан, чтобы указать, что лексический анализ повторяется после предварительной обработки, и в этом случае pp-токен 0xe+foo станет тремя токенами фазы 7, целочисленным литералом 0x3, оператором + и идентификатором foo. Стандарт C не был написан таким образом; вместо этого он требует, чтобы 0xe+foo был преобразован в токен фазы 7 не замужем, и нет соответствующей лексической продукции, поэтому это синтаксическая ошибка. Я верю, но не на 100% уверен, что стандарт C++ такой же.

zwol 11.12.2018 19:29

@zwol: С ++ то же самое.

rici 15.12.2018 03:05

@rici Полезно знать, спасибо.

zwol 16.12.2018 16:33

Другие вопросы по теме