通过的条件包含具有包含表达式和具有属性表达式的行为不同


#include <iostream>
#define Abc likely
# if __has_cpp_attribute(Abc)
#define Pn 0
#endif 
#if __has_cpp_attribute(likely)
#ifndef Pn
#define Pn 1
#endif
#endif
int main(){
std::cout<< Pn;
}

在本例中,GCC打印0,而Clang打印1。根据[cpp.ccond]p5

如果实现支持具有通过解释pp令牌指定的名称的属性,则每个has属性表达式将由与整数文本形式匹配的非零pp数替换,在宏扩展后被替换为属性令牌,否则被替换为0。如果pp令牌与属性令牌的形式不匹配,则程序格式错误。

因此,指令# if __has_cpp_attribute(Abc)的行为应与#if __has_cpp_attribute(likely)相同。GCC有正确的行为。再次考虑这个例子

#include <iostream>
#define Head <iostream>
# if __has_include(Head)
#define Pn 0
#endif 
#ifndef Pn
#define Pn 1
#endif
int main(){
std::cout<< Pn;
}

在本例中,两个编译器都打印0。然而,根据[cpp.cond]p4

在每个包含的has-include表达式中,由带括号的预处理令牌序列标识的头文件或源文件将被搜索,就好像该预处理令牌顺序是#include指令中的pp令牌一样,,除非不执行进一步的宏扩展。如果这样的指令不能满足#include指令的语法要求,那么程序就是格式错误的。如果搜索源文件成功,则has-include表达式的计算结果为1,如果搜索失败,则计算结果为0。

注意粗体字,这意味着Head不会被<iostream>取代,没有这样的源文件。因此,Pn应该改为1。它会被认为是GCC和Clang的一个bug吗?

我不确定下面的答案是否正确。我暂时把它留作参考。


我认为第二个例子不符合包含表达式语法。如果你看一下[cpp.cond],这里提到了两种形式,它们被进一步细分为多个案例,也指[lex.header]。

收集可能的表格并将其组合在这里进行演示,我们得到:

__has_include(<...>)
__has_include("...")
__has_include(string-literal)

其中CCD_ 10作为一些占位符,CCD_。您的表单__has_include(Head)不是这些,因为Head既不是以"<开头,也不是字符串文字。

[cpp.cond]/3确实提到,如果的两个语法选择中的第一个包含表达式不匹配,则考虑第二个,预处理器标记将像普通文本一样处理,这可能意味着它们是宏扩展的。然而,我不清楚这是应该在应用上述语法规则之前引用()之间的所有预处理器令牌,还是仅仅引用__has_include(<h-pp-tokens>)形式中的h-pp-tokens。在前一种情况下,编译器返回0是正确的。

然而,后一种情况对我来说更有意义,尤其是当与#include的语法规则进行比较时,后者使用类似的形式,但不是#include <h-pp-tokens>,最后一种形式是#include pp-tokens。[cpp.include]

[cpp.cond]/7规定,标识符__has_include不应出现在子条款中未提及的任何上下文中。我认为不应这里的意思是格式不正确,在这种情况下,程序不应在没有诊断的情况下编译。如果它意味着否则未定义的行为,则所有编译器都是正确的。


对于第一个例子,我认为你是对的。Clang最近修复了一份关于宏扩展的错误报告,如果你在编译器资源管理器上选择Clang trunk,结果将与GCC一致。

最新更新