Consider this code:
#define FOO A::
struct A
{
int x;
};
int FOO *ptr = &A::x;
Clang (18.1.0 with no flags) emits a warning:
<source>:8:5: warning: '::' and '*' tokens forming pointer to member type appear in different macro expansion contexts [-Wcompound-token-split-by-macro]
MSVC and GCC accept the code as is.
I thought it wants me to paste ::
and *
together, but that triggers a hard error in all three compilers:
#define CAT(x, y) CAT_(x, y)
#define CAT_(x, y) x##y
int CAT(FOO,*) ptr = &A::x;
error: pasting formed '::*', an invalid preprocessing token
Is the first snippet legal or not, according to the standard? If yes, is there any way to silence this warning, other than a #pragma
?
::*
is not a single pp-token. The operators that are pp-tokens are given in [lex.operators]/1. There, ::
and *
are two separate tokens, and there's no entry for ::*
. So it's illegal to try to concatenate two pp-tokens to give ::*
because the token pasting operator must produce a single pp-token as its result ([cpp.concat]/3).
Of course two separate tokens are allowed to come from two different macro expansions; it's no different from something like
#define foo const
#define bar int
foo bar x; // const int x;
const
and int
are two separate tokens that, when they occur next to each other in this context, imply the type const int
. Similarly, when the ::
token is followed by the *
token in some contexts, you declare a pointer-to-member or name a pointer-to-member type.
Indeed, ::
and *
can be separated by whitespace without changing their meaning. This is the case for any distinct tokens in the language after preprocessing is complete; see [lex.phases]/1.7.
There's no such thing as a "compound token" in the C++ standard. The Clang devs made it up. You can try searching for "compound token" in https://eel.is/c++draft/full and you'll find nothing. It seems that the Clang devs find it confusing and possibly unintended when the ::
and the *
come from different macro expansions or are separated by whitespace. Indeed, I am sure this will be confusing to some people, but I am sure there are also legitimate reasons to do it, so I would recommend disabling this warning.