根据C++标准语法解析数字字面量的不一致性问题

12
阅读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+1(...)时,会发现未找到operator"" _e(...)。这似乎是因为编译器首先将标记解析为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]
    }

这是标准的正确解读吗?如果是,编译器应该处理这个边角案例,这种情况可能比较罕见,但是否合理呢?


1
顺便提一下,MSVC编译这两种情况都没问题。 - DeiDei
@Aconcagua - 实际上,_pp-number_ 词法包括下划线的 identifier-nondigit 和 _nondigit_。 - Andy G
这不是一个答案,因为我不能百分之百确定它是否适用于C++17,但在C中存在类似的边角情况,解释是标准实际上要求它是一个错误:每个pp-token都必须转换为一个且仅一个phase-7 token,编译器不允许像你所说的“回滚”。 - zwol
@AndyG 噢,你似乎是对的,抱歉 - 但 + 没有被包括在内?有趣的是:p/P 被包括进去了,但是 0x 前缀也没有... - Aconcagua
@ShafikYaghmour 嗯,这将需要将1.e+E+p+P+e解析为pp-number...我认为这些构造规则太泛泛了,下一个标准可能会更精确(有两条路径:只有在尚未出现标识符的情况下才允许e sign,反之亦然)... - Aconcagua
显示剩余7条评论
1个回答

3
你遇到了最长匹配规则,即词法分析器尽可能多地取字符以形成一个有效的标记。
这在第[lex.pptoken]p3节中有所涵盖,其中写道(重点是我的):

否则,下一个预处理标记是可以构成预处理标记的最长字符序列,即使这会导致进一步的词法分析失败,但头文件名([lex.header])仅在#include指令中形成。

并包括几个例子:

[ 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  ]

这个规则会影响到其他一些知名的情况,例如a+++++btokens >= which required a fix to allow
参考pp-token语法如下:
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

1
自从你再次提到我,我已经多年没有仔细阅读C++标准了,特别是我不知道是否有关于用户定义字面量的特殊规则。如果您考虑到5.1.1.2p1#7的通常解释“每个预处理记号都转换为一个记号”,这种解释对于C是正确的,意味着_compiler_不应将单个pp-token拆分为两个第7阶段的token,即使这会导致有效的解析。 - zwol
在第7个阶段上的最大啃字法将从示例4生成0xe + foo,因为0xe是整数字面值令牌,而整数字面值的产生不允许在那一点上有+ - zwol
现在出于好奇:0x 中的 x 是如何处理的?在解析时,它会被视为标识符非数字字符吗? - Aconcagua
1
@Aconcagua 我的意思是这就是整个问题。0xe+foo是一个预处理标记。标准本可以写成在预处理之后重复执行词法分析,这种情况下pp-token 0xe+foo将变成三个阶段7标记,即整数字面值0x3、运算符+和标识符foo。C标准并非如此编写;相反,它要求将0xe+foo转换为单个阶段7标记,并且没有与之匹配的词法产生式,因此它是语法错误。我相信,但不确定,C++标准也是这样。 - zwol
@rici 很好知道,谢谢。 - zwol
显示剩余3条评论

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接