#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一致。