c++macrosvisual-studio-2022preprocessor-directive

Understanding why certain preprocessor macro overloads don't work


I'm currently working in VS2022 writing a set of macros using the variadic macro tricks described in Overloading Macro on Number of Arguments. Specifically, I'm trying to do 3 things with scoped types: one is to generate the scoped type itself, one is to create a string literal of the scoped type, and one is trying to create a variable name from the scoped type by replacing :: with _. So far my macros looks like this:

#define GET_ARGS(_1, _2, _3, _4, _5, ARGS, ...) ARGS

// these generate the actual scoped name
#define SCOPE1(_1) _1
#define SCOPE2(_1, ...) _1##::## SCOPE1(__VA_ARGS__)
#define SCOPE3(_1, ...) _1##::## SCOPE2(__VA_ARGS__)
#define SCOPE4(_1, ...) _1##::## SCOPE3(__VA_ARGS__)
#define SCOPE5(_1, ...) _1##::## SCOPE4(__VA_ARGS__)
#define GET_SCOPE(...) GET_ARGS(__VA_ARGS__, SCOPE5, SCOPE4, SCOPE3, SCOPE2, SCOPE1)(__VA_ARGS__)

// these generate a string of the scoped name
#define STR1(_1) #_1
#define STR2(_1, ...) #_1 "::" STR1(__VA_ARGS__)
#define STR3(_1, ...) #_1 "::" STR2(__VA_ARGS__)
#define STR4(_1, ...) #_1 "::" STR3(__VA_ARGS__)
#define STR5(_1, ...) #_1 "::" STR4(__VA_ARGS__)
#define GET_STR(...) GET_ARGS(__VA_ARGS__, STR5, STR4, STR3, STR2, STR1)(__VA_ARGS__)

// these generate a name for a variable
#define VAR1(_1) _1
#define VAR2(_1, ...) _1##_## VAR1(__VA_ARGS__)
#define VAR3(_1, ...) _1##_## VAR2(__VA_ARGS__)
#define VAR4(_1, ...) _1##_## VAR3(__VA_ARGS__)
#define VAR5(_1, ...) _1##_## VAR4(__VA_ARGS__)
#define GET_VAR(...) GET_ARGS(__VA_ARGS__, VAR5, VAR4, VAR3, VAR2, VAR1)(__VA_ARGS__)

To test this, I have this simple test code:

namespace ns1
{
namespace ns2
{
struct cls {};
}
}

GET_SCOPE(int) i;
GET_SCOPE(ns1, ns2, cls) v;

const char* sv = GET_STR(ns1, ns2, cls);

int GET_VAR(ns1, ns2, cls);

The first three cases work great, and produce exactly the results I want:

int i;
ns1::ns2::cls v;

const char* sv = "ns1" "::" "ns2" "::" "cls";

However, the last case fails for reasons I don't understand. The IDE resolves it as int ns1_VAR2(ns2, cls); For the record, I do have /Zc:preprocessor enabled to make the preprocessor compliant. Can anyone explain why only the one case fails when the others succeed? I've also tried replacing _ with other characters just in case that was the issue, but there was no change.


Solution

  • The problem is that ## token pasting happens before other macros in the body are scanned for expansion. If you want those macros to be expanded first, you need to get them into arguments of other macros that don't use ## directly (as the ## also suppresses the argument prescan). So you need:

    // these generate a name for a variable
    #define CONCAT_UNDERSCORE(_1, _2) _1##_##_2
    #define EXPAND_CONCAT_UNDERSCORE(_1, _2) CONCAT_UNDERSCORE(_1, _2)
    #define VAR1(_1) _1
    #define VAR2(_1, ...) EXPAND_CONCAT_UNDERSCORE(_1, VAR1(__VA_ARGS__))
    #define VAR3(_1, ...) EXPAND_CONCAT_UNDERSCORE(_1, VAR2(__VA_ARGS__))
    #define VAR4(_1, ...) EXPAND_CONCAT_UNDERSCORE(_1, VAR3(__VA_ARGS__))
    #define VAR5(_1, ...) EXPAND_CONCAT_UNDERSCORE(_1, VAR4(__VA_ARGS__))
    #define GET_VAR(...) GET_ARGS(__VA_ARGS__, VAR5, VAR4, VAR3, VAR2, VAR1)(__VA_ARGS__)
    

    You may be curious as to why it apparently "works" for your SCOPE case. That is because the ## are actually incorrect there, as :: is a separate token (so a scoped name like A::B is actually 3 tokens, not a single token). You can't actually paste a :: to an identifier (the result is not a token), so this actually gives undefined behavior. Most preprocessors will just (silently) resplit the byte sequence into tokens (undoing the ##), though some will give you a warning about it. You can simply delete the ##s in the SCOPE macros and it will work just fine (and won't give undefined behavior).