c++language-lawyerc-preprocessor

Can `::` and `*` forming a member pointer type come from different macro expansions, or must they appear as a single token?


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?


Solution

  • ::* 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.