Search code examples
c++macros

what's difference between passing __VA_ARGS__ and ##__VA_ARGS__ between macro


This is my macro define,I want to make a simple reflection of the struct/class members

template <typename T>
struct ReflectionTypeStringTraits {
    static const char* value;
};

template <>
struct ReflectionTypeStringTraits<int> {
    static const char* value;
};
const char* ReflectionTypeStringTraits<int>::value = "int";

template <>
struct ReflectionTypeStringTraits<std::string> {
    static const char* value;
};
const char* ReflectionTypeStringTraits<std::string>::value = "string";

#define REFLECTION_MACRO_CALL_VEC(CALL1, CALL2, CALL3, CALL4, CALL, ...) CALL

#define REFLECTION_TYPE_MEMBER_TYPE_MAP_0(struct_name)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) { #member_name, ReflectionTypeStringTraits<decltype(struct_name::member_name)>::value },
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_2(struct_name, member_name, ...)  REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name,  ##__VA_ARGS__)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_3(struct_name, member_name, ...) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_2(struct_name,  ##__VA_ARGS__)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_4(struct_name, member_name, ...) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_3(struct_name,  ##__VA_ARGS__)

#define REFLECTION_TYPE_MEMBER_TYPE_MAP(struct_name, ...) \
    REFLECTION_MACRO_CALL_VEC(__VA_ARGS__, \
    REFLECTION_TYPE_MEMBER_TYPE_MAP_4, REFLECTION_TYPE_MEMBER_TYPE_MAP_3, REFLECTION_TYPE_MEMBER_TYPE_MAP_2, REFLECTION_TYPE_MEMBER_TYPE_MAP_1, \
    REFLECTION_TYPE_MEMBER_TYPE_MAP_0)(struct_name, __VA_ARGS__)


#define MAKE_REFLECTION(struct_name, ...) \
struct struct_name##_Reflection { \
    struct_name##_Reflection(struct_name& obj) : this_(std::make_shared<struct_name>(obj)) {} \
    std::shared_ptr<struct_name> this_; \
    static std::map<std::string, std::string> fieldMap; \
}; \
std::map<std::string, std::string> struct_name##_Reflection::fieldMap = { \
    REFLECTION_TYPE_MEMBER_TYPE_MAP(struct_name, __VA_ARGS__) \
}

#define GET_REFLECTION(struct_name, obj) struct_name##_Reflection(obj)

This is my test

struct Test {
    int a;
    int b;
    std::string c;
};

MAKE_REFLECTION(Test, a, b, c);

The macro is expand to

struct Test_Reflection {
    Test_Reflection(Test& obj) : this_(std::make_shared<Test>(obj)) {} 
    std::shared_ptr<Test> this_; 
    static std::map<std::string, std::string> fieldMap;
};
std::map<std::string, std::string> Test_Reflection::fieldMap = { 
    { "a,b,c", ReflectionTypeStringTraits<decltype(Test::a,b,c)>::value }, 
};

But what I want is

struct Test_Reflection {
    Test_Reflection(Test& obj) : this_(std::make_shared<Test>(obj)) {} 
    std::shared_ptr<Test> this_; 
    static std::map<std::string, std::string> fieldMap;
};
std::map<std::string, std::string> Test_Reflection::fieldMap = { 
    { "a", ReflectionTypeStringTraits<decltype(Test::a)>::value }, 
    { "b", ReflectionTypeStringTraits<decltype(Test::b)>::value }, 
    { "c", ReflectionTypeStringTraits<decltype(Test::c)>::value }, 
};

If I call REFLECTION_TYPE_MEMBER_TYPE_MAP_3 directly, It will get correct expand

REFLECTION_TYPE_MEMBER_TYPE_MAP_3(Test, a, b, c);
/* expand success
{ "a", ReflectionTypeStringTraits<decltype(Test::a)>::value }, 
{ "b", ReflectionTypeStringTraits<decltype(Test::b)>::value }, 
{ "c", ReflectionTypeStringTraits<decltype(Test::c)>::value }, ;
*/

If I remove the ## of VA_ARGS in REFLECTION_TYPE_MEMBER_TYPE_MAP_N, the macro parameter will become one

#define REFLECTION_TYPE_MEMBER_TYPE_MAP_0(struct_name)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) { #member_name, ReflectionTypeStringTraits<decltype(struct_name::member_name)>::value },
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_2(struct_name, member_name, ...)  REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name,  __VA_ARGS__)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_3(struct_name, member_name, ...) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_2(struct_name,  __VA_ARGS__)
#define REFLECTION_TYPE_MEMBER_TYPE_MAP_4(struct_name, member_name, ...) REFLECTION_TYPE_MEMBER_TYPE_MAP_1(struct_name, member_name) REFLECTION_TYPE_MEMBER_TYPE_MAP_3(struct_name,  __VA_ARGS__)

// after change the ##__VA_ARGS__ to __VA_ARGS__
REFLECTION_TYPE_MEMBER_TYPE_MAP_3(Test, a, b, c);
/* only first parameter is correct
{ "a", ReflectionTypeStringTraits<decltype(Test::a)>::value }, 
{ "b, c", ReflectionTypeStringTraits<decltype(Test::b, c)>::value }, 
{ , ReflectionTypeStringTraits<decltype(Test::)>::value }, ;
*/

Solution

  • The default MSVC preprocessor is just buggy. Adding /Zc:preprocessor to switch to the modern preprocessor fixes the issue.

    GCC and Clang do the right thing without any flags in this case.


    I don't think learning to accomodate the legacy preprocessor is worth one's time.

    You can add following check to explicitly detect the legacy preprocessor and avoid the obscure errors if you forget to enable the modern one:

    #if defined(_MSC_VER) && !defined(__clang__) && (!defined(_MSVC_TRADITIONAL) || _MSVC_TRADITIONAL == 1)
    #error The standard-conformant MSVC preprocessor is required, enable it with `/Zc:preprocessor`.
    #endif