Condition inclusion through has-include-expression and has-attribute-expression behaves differently

Viewed 115
#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;
}

For this example, GCC prints 0 while Clang prints 1. According to [cpp.cond] p5

Each has-attribute-expression is replaced by a non-zero pp-number matching the form of an integer-literal if the implementation supports an attribute with the name specified by interpreting the pp-tokens, after macro expansion, as an attribute-token, and by 0 otherwise. The program is ill-formed if the pp-tokens do not match the form of an attribute-token.

So, the directive # if __has_cpp_attribute(Abc) should behave the same as #if __has_cpp_attribute(likely). GCC has the right behavior. Again, consider this example

#include <iostream>
#define Head <iostream>
# if __has_include(Head)
#define Pn 0
#endif 

#ifndef Pn
#define Pn 1
#endif
int main(){
   std::cout<< Pn;
}

In this example, both compilers print 0. However, according to [cpp.cond] p4

The header or source file identified by the parenthesized preprocessing token sequence in each contained has-include-expression is searched for as if that preprocessing token sequence were the pp-tokens in a #include directive, except that no further macro expansion is performed. If such a directive would not satisfy the syntactic requirements of a #include directive, the program is ill-formed. The has-include-expression evaluates to 1 if the search for the source file succeeds, and to 0 if the search fails.

Note the bold wording, which means Head won't be replaced by <iostream>, there is no such a source file. Hence, Pn should be 1 instead. Could it be considered a bug of GCC and Clang?

1 Answers

I am not sure that the answer below is correct. I will leave it up for reference for now.


I think the second example does not fit the has-include-expression grammar. If you look at [cpp.cond] there are two forms mentioned, which are further subdivided into multiple cases, referring also to [lex.header].

Collecting the possible forms and combining them here for presentation, we get:

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

with ... as some placeholder and string-literal any string literal. Your form __has_include(Head) is none of these, since Head neither starts with ", nor <, nor is it a string literal.

[cpp.cond]/3 does mention that if the first of the two syntax choices for has-include-expression does not match, the second is considered and the preprocessor tokens are processed like normal text, presumably meaning they are macro-expanded. However it is not clear to me whether this is supposed to reference all preprocessor tokens between ( and ) before the above-mentioned grammar rules are applied or just the h-pp-tokens in the __has_include(<h-pp-tokens>) form. In the former case, the compilers would be correct in returning 0.

However, the latter case makes more sense to me, especially when comparing e.g. to the grammar rule for #include, which uses similar forms, but instead of #include <h-pp-tokens> the last form is #include pp-tokens. [cpp.include]

[cpp.cond]/7 says that the identifier __has_include shall not appear in any context not mentioned in the subclause. I would think that shall not here means otherwise ill-formed, in which case the program should not compile without diagnostic. If it means otherwise undefined behavior, then all compilers are correct.


For the first example, I think you are right. Clang has a recently-fixed bug report regarding the macro expansion here and if you choose Clang trunk on compiler explorer, the result will coincide with GCC already now.

Related