c++c-preprocessorvariadic-macrostoken-pasting-operator

why can't this macro be expanded when using wrapping macro of token pasting operator multiple times


I want to implement some function related to reflection in c++, but some issue occurred when I want to expand macro which be concatenated by two macro has been expanded.

I try reproducing this issue in the smallest code range. This is the issued code I reproduced below:

#include <iostream>

#define M_EXPAND(x) x
#define STRINGIZE_(x) #x
#define STRINGIZE(x) STRINGIZE_(x)
#define CONCAT_(left, right) left ## right
#define M_FUNC(...) std::cout << #__VA_ARGS__ << std::endl;
#define EXEC_ACTION_N(action, ...) action(__VA_ARGS__)
#define EXEC_ACTION(action, ...) CONCAT_(EXEC_ACTION_, N) (action, ##__VA_ARGS__)
#define EXEC_ACTION_NO_CONCAT(action, ...) EXEC_ACTION_##N (action, ##__VA_ARGS__)
#define EXEC_ACTION_NO_PASTING(action, ...) EXEC_ACTION_N(action, ##__VA_ARGS__)
#define CALL_FUNC(...) EXEC_ACTION(M_FUNC, ##__VA_ARGS__)
#define CALL_FUNC_NO_CONCAT(...) EXEC_ACTION_NO_CONCAT(M_FUNC, ##__VA_ARGS__)
#define CALL_FUNC_NO_PASTING(...) EXEC_ACTION_NO_PASTING(M_FUNC, ##__VA_ARGS__)


int main()
{
    // output: CONCAT_(EXEC_ACTION_, N)(M_FUNC,foo, bar)
    std::cout << STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC(foo, bar)))) << std::endl;
    // output: std::cout << "foo, bar" << std::endl;
    std::cout << STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC_NO_CONCAT(foo, bar)))) << std::endl;
    // output: std::cout << "foo, bar" << std::endl;
    std::cout << STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC_NO_PASTING(foo, bar)))) << std::endl;
    // output: std::cout << "foo, bar" << std::endl;
    std::cout << STRINGIZE(CALL_FUNC(foo, bar)) << std::endl;
}

My goal was to get this macro M_EXPAND(CONCAT_(CALL_, FUNC(foo, bar))) to expand correctly like this std::cout << "foo, bar" << std::endl;, but it actually expanded as this CONCAT_(EXEC_ACTION_, N)(M_FUNC,foo, bar), I don't know why that's happening.

I don't know why the C preprocessor can't expand/invocate several macro which is be concatenated by wrapping macro contains token pasting operator when the chain of invocation of macro is too long.

I test this issue that it works fine when only one macro which is be concatenated by macro CONCAT_ is called:

// this macro doesn't use macro CONCAT_
#define EXEC_ACTION_NO_CONCAT(action, ...) EXEC_ACTION_##N (action, ##__VA_ARGS__)
#define CALL_FUNC_NO_CONCAT(...) EXEC_ACTION_NO_CONCAT(M_FUNC, ##__VA_ARGS__)
// output: std::cout << "foo, bar" << std::endl;
std::cout << STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC_NO_CONCAT(foo, bar)))) << std::endl;

besides, it also works fine when I doesn't use token pasting operator:

// this macro doesn't use token pasting operator
#define EXEC_ACTION_NO_PASTING(action, ...) EXEC_ACTION_N(action, ##__VA_ARGS__)
#define CALL_FUNC_NO_PASTING(...) EXEC_ACTION_NO_PASTING(M_FUNC, ##__VA_ARGS__)
// output: std::cout << "foo, bar" << std::endl;
std::cout << STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC_NO_PASTING(foo, bar)))) << std::endl;

My test environment is

os: Win10 X64 professional
compiler: (MinGW-W64 x86_64-ucrt-posix-seh, built by Brecht Sanders) 13.1.0
compile cli: `g++ D:\xxx\bug_test.cc -o D:\xxx\bin\bug_test.exe -g -Wall -static-libgcc -std=gnu++17`

The solution I'd like is to be able to get the macro M_EXPAND(CONCAT_(CALL_, FUNC(foo, bar))) to expand correctly like this std::cout << "foo, bar" << std::endl; without removing related to the wrapping macro CONCAT_ , because the reflection funcion I want to implement need the usage of token pasting operator.

Can someone help me solve this problem? I would appreciate this!

--------------this is the whole code of which I want to implement.--------------

my_reflection.h

#ifndef MY_REFLECTION_H_
#define MY_REFLECTION_H_
#include <string>
#include <tuple>

#define M_EXPAND(x) x
#define NUMBER_OF_ARGS_(_1, _2, _3, _4, _5, _6, _7, _8, _9, _10, _11, value, ...) value
#ifdef _MSC_VER // Microsoft compilers
#define NUMBER_OF_ARGS_2(...) NUMBER_OF_(__VA_ARGS__, 5, 4, 3, 2, 1, 0)
#define NOA_AUGMENTER(...) unused, __VA_ARGS__
#define NUMBER_OF_ARGS_AUGMENT(...) M_EXPAND(NUMBER_OF_ARGS_(__VA_ARGS__, 5, 4, 3, 2, 1, 0))
#define NUMBER_OF_ARGS(...) NUMBER_OF_ARGS_AUGMENT(NOA_AUGMENTER(__VA_ARGS__))
#else
#define NUMBER_OF_ARGS(...) NUMBER_OF_ARGS_(0, ##__VA_ARGS__, 10, 9, 8, 7,6, 5, 4, 3, 2, 1, 0)
#endif

#define INDEX_FOR_ELEMENT_1(...) 0
#define INDEX_FOR_ELEMENT_2_(_1, N, ...) N
#define INDEX_FOR_ELEMENT_2(...) INDEX_FOR_ELEMENT_2_(__VA_ARGS__ 0, 1)
#define INDEX_FOR_ELEMENT_3_(_1, _2, N, ...) N
#define INDEX_FOR_ELEMENT_3(...) INDEX_FOR_ELEMENT_3_(__VA_ARGS__ 0, 1, 2)
#define INDEX_FOR_ELEMENT_4_(_1, _2, _3, N, ...) N
#define INDEX_FOR_ELEMENT_4(...) INDEX_FOR_ELEMENT_4_(__VA_ARGS__ 0, 1, 2, 3)
#define INDEX_FOR_ELEMENT_5_(_1, _2, _3, _4, N, ...) N
#define INDEX_FOR_ELEMENT_5(...) INDEX_FOR_ELEMENT_5_(__VA_ARGS__ 0, 1, 2, 3, 4)
#define INDEX_FOR_ELEMENT_6_(_1, _2, _3, _4, _5, N, ...) N
#define INDEX_FOR_ELEMENT_6(...) INDEX_FOR_ELEMENT_6_(__VA_ARGS__ 0, 1, 2, 3, 4, 5)
#define INDEX_FOR_ELEMENT_7_(_1, _2, _3, _4, _5, _6, N, ...) N
#define INDEX_FOR_ELEMENT_7(...) INDEX_FOR_ELEMENT_7_(__VA_ARGS__ 0, 1, 2, 3, 4, 5, 6)
#define INDEX_FOR_ELEMENT_8_(_1, _2, _3, _4, _5, _6, _7, N, ...) N
#define INDEX_FOR_ELEMENT_8(...) INDEX_FOR_ELEMENT_8_(__VA_ARGS__ 0, 1, 2, 3, 4, 5, 6, 7)
#define INDEX_FOR_ELEMENT_9_(_1, _2, _3, _4, _5, _6, _7, _8, N, ...) N
#define INDEX_FOR_ELEMENT_9(...) INDEX_FOR_ELEMENT_9_(__VA_ARGS__ 0, 1, 2, 3, 4, 5, 6, 7, 8)
#define INDEX_FOR_ELEMENT_10_(_1, _2, _3, _4, _5, _6, _7, _8, _9, N, ...) N
#define INDEX_FOR_ELEMENT_10(...) INDEX_FOR_ELEMENT_10_(__VA_ARGS__ 0, 1, 2, 3, 5, 6, 7, 8, 9)

#define STRINGIZE_(x) #x
#define STRINGIZE(x) STRINGIZE_(x)
#define CONCAT_(left, right) left ## right
#define CONCAT(left, right) CONCAT_(left, right)

#define ACTION_FOR_EACH(action, extra_data, ...) CONCAT(ACTION_FOR_EACH_, NUMBER_OF_ARGS(__VA_ARGS__))(action, extra_data, CONCAT(INDEX_FOR_ELEMENT_, NUMBER_OF_ARGS(__VA_ARGS__)), ##__VA_ARGS__)

#define ACTION_FOR_EACH_0(...)
#define ACTION_FOR_EACH_1(action, extra_data, func_for_index, element) action(extra_data, func_for_index(), element)
#define ACTION_FOR_EACH_2(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_1(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_3(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_2(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_4(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_3(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_5(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_4(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_6(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_5(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_7(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_6(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_8(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_7(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_9(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_8(action, extra_data, func_for_index, __VA_ARGS__)
#define ACTION_FOR_EACH_10(action, extra_data, func_for_index, element, ...) action(extra_data, func_for_index(__VA_ARGS__,), element) ACTION_FOR_EACH_9(action, extra_data, func_for_index, __VA_ARGS__)


template<class T>
class TypeInfo;

template<class T>
TypeInfo<T> getTypeInfo(T obj)
{
    return obj;
}
template<class T>
TypeInfo<T> getTypeInfo()
{
    return {};
}

template<class T1, class T2>
class MetaInfoInterface
{
protected:
    T2 instance_;
public:
    using Type = T1;
    virtual std::string getName() = 0;
    virtual T1 getValue(const T2& obj) = 0;
    virtual T1 getValue() = 0;
};

template<class T, class ...MemberTypes>
class TypeInfoInterface: public MetaInfoInterface<T, T>
{
public:
    T getValue(const T& obj) override
    {
        return obj;
    }
    T getValue() override
    {
        return this->instance_;
    }
    std::tuple<MemberTypes...> getMembers()
    {
        return std::make_tuple(MemberTypes(this->instance_)...);
    }
};

namespace metadata {}

#define OPEN_NAMESPACE(space_name)\
    namespace metadata { namespace space_name {
#define CLOSE_NAMESPACE }}

#define GENERATE_FIELD_INFO(unused, i, field_name)\
    class FieldInfo__##field_name: public MetaInfoInterface<decltype(ClassType::field_name), ClassType> { public: \
        std::string getName() override { return #field_name; }\
        Type getValue(const ClassType& obj) override { return obj.field_name; }\
        Type getValue() override { return (this->instance_).field_name; }\
        FieldInfo__##field_name() = default;\
        FieldInfo__##field_name(const ClassType& obj) { this->instance_ = obj; }};

#define GENERATE_FUNCTION_INFO(unused, i, function_name)


#define GEN_INFOS_field(...)\
    ACTION_FOR_EACH(GENERATE_FIELD_INFO, unused, ##__VA_ARGS__)
#define GEN_INFOS_func(...)\
    ACTION_FOR_EACH(GENERATE_FUNCTION_INFO, unused, ##__VA_ARGS__)
#define GEN_MEMBERS(unused, i, section)\
    M_EXPAND(CONCAT(GEN_INFOS_, section))

#define GET_INFOS_TYPE_field(...)\
    , FieldInfo__##field_name
#define GET_INFOS_TYPE_func(...)
#define GET_MEMBERS_TYPE(unused, i, section)\
    M_EXPAND(CONCAT(GET_INFOS_TYPE_, section))


#define GENERATE_TYPE_INFO(class_type, ...) \
    ACTION_FOR_EACH(GEN_MEMBERS, unused, ##__VA_ARGS__)\
    template<> class TypeInfo<ClassType>: \
    public TypeInfoInterface<ClassType ACTION_FOR_EACH(GET_MEMBERS_TYPE, unused, ##__VA_ARGS__)> { public:\
    std::string getName() override { return #class_type; }\
    TypeInfo() = default;\
    TypeInfo(const ClassType& obj) { this->instance_ = obj; }};

#define REGISTER_CLASS(class_type, ...)\
    OPEN_NAMESPACE(type_##class_type##_space)\
    using ClassType = class_type;\
    GENERATE_TYPE_INFO(class_type, ##__VA_ARGS__)\
    CLOSE_NAMESPACE

#endif

my_reflection_test.cc

#include "my_reflection.h"
#include <iostream>
#include <vector>
#include <type_traits>

class Person
{
public:
    std::string name_;
    bool sex_;
    unsigned birth_year_;
    int age_;
    float weight_;
};

int main()
{
    // unexpected output: CONCAT_(ACTION_FOR_EACH_, 5)(GENERATE_FIELD_INFO, unused, CONCAT_(INDEX_FOR_ELEMENT_, 5),name_, sex_, age_, birth_year_, weight_)
    std::cout << STRINGIZE(CONCAT_(GEN_INFOS_, field(name_, sex_, age_, birth_year_, weight_))) << std::endl;
    /**
     * expected output:
     * 
     * (class FieldInfo__name_: public MetaInfoInterface<decltype(ClassType::name_), ClassType> { public: std::string getName() override { return "name_"; } Type getValue(const ClassType& obj) override { return obj.name_; } Type getValue() override { return (this->instance_).name_; } FieldInfo__name_() = default; FieldInfo__name_(const ClassType& obj) { this->instance_ = obj; }};
     * class FieldInfo__sex_: public MetaInfoInterface<decltype(ClassType::sex_), ClassType> { public: std::string getName() override { return "sex_"; } Type getValue(const ClassType& obj) override { return obj.sex_; } Type getValue() override { return (this->instance_).sex_; } FieldInfo__sex_() = default; FieldInfo__sex_(const ClassType& obj) { this->instance_ = obj; }}; 
     * class FieldInfo__age_: public MetaInfoInterface<decltype(ClassType::age_), ClassType> { public: std::string getName() override { return "age_"; } Type getValue(const ClassType& obj) override { return obj.age_; } Type getValue() override { return (this->instance_).age_; } FieldInfo__age_() = default; FieldInfo__age_(const ClassType& obj) { this->instance_ = obj; }}; 
     * class FieldInfo__birth_year_: public MetaInfoInterface<decltype(ClassType::birth_year_), ClassType> { public: std::string getName() override { return "birth_year_"; } Type getValue(const ClassType& obj) override { return obj.birth_year_; } Type getValue() override { return (this->instance_).birth_year_; } FieldInfo__birth_year_() = default; FieldInfo__birth_year_(const ClassType& obj) { this->instance_ = obj; }}; 
     * class FieldInfo__weight_: public MetaInfoInterface<decltype(ClassType::weight_), ClassType> { public: std::string getName() override { return "weight_"; } Type getValue(const ClassType& obj) override { return obj.weight_; } Type getValue() override { return (this->instance_).weight_; } FieldInfo__weight_() = default; FieldInfo__weight_(const ClassType& obj) { this->instance_ = obj; }};)
     */
    std::cout << STRINGIZE((GEN_INFOS_field(name_, sex_, age_, birth_year_, weight_))) << std::endl;
    /**
     * unexpected output:
     * 
     * (namespace metadata { namespace type_Person_space { using ClassType = Person; 
     * ACTION_FOR_EACH(GENERATE_FIELD_INFO, unused,name_, sex_, age_, birth_year_, weight_) 
     * ACTION_FOR_EACH(GENERATE_FUNCTION_INFO, unused,prints, getNum)
     * template<> class TypeInfo<ClassType>: public TypeInfoInterface<ClassType , FieldInfo__field_name > { public: std::string getName() override { return "Person"; } TypeInfo() = default; TypeInfo(const ClassType& obj) { this->instance_ = obj; }}; }})
     * 
     */
    std::cout << STRINGIZE((M_EXPAND(REGISTER_CLASS(
                    Person,
                    field(name_, sex_, age_, birth_year_, weight_),
                    func(prints, getNum)
                )))) << std::endl;
}

As you can see from the above code, I want this macro GEN_INFOS_field to be fully expanded correctly to generate all the meta information related to the class members. But strangely, the macro expansion stops halfway through, just remain the code CONCAT_(ACTION_FOR_EACH_, 5)(GENERATE_FIELD_INFO, unused, CONCAT_(INDEX_FOR_ELEMENT_, 5),name_, sex_, age_, birth_year_, weight_). So I'm trying to figure out why?


Solution

  • #define EXEC_ACTION(action, ...) CONCAT_(EXEC_ACTION_, N) (action, ##__VA_ARGS__)
    
    CONCAT_(CALL_, FUNC(foo, bar))
    

    CONCAT_ is painted blue inside CONCAT_. The trivially simple fix is just to pick another macro name. See CONCAT2 below:

    #define M_EXPAND(x) x
    #define STRINGIZE_(x) #x
    #define STRINGIZE(x) STRINGIZE_(x)
    #define CONCAT_(left, right) left ## right
    #define CONCAT2(left, right) left ## right
    #define M_FUNC(...) std::cout << #__VA_ARGS__ << std::endl;
    #define EXEC_ACTION_N(action, ...) action(__VA_ARGS__)
    #define EXEC_ACTION(action, ...) CONCAT2(EXEC_ACTION_, N) (action, ##__VA_ARGS__)
    #define EXEC_ACTION_NO_CONCAT(action, ...) EXEC_ACTION_##N (action, ##__VA_ARGS__)
    #define EXEC_ACTION_NO_PASTING(action, ...) EXEC_ACTION_N(action, ##__VA_ARGS__)
    #define CALL_FUNC(...) EXEC_ACTION(M_FUNC, ##__VA_ARGS__)
    #define CALL_FUNC_NO_CONCAT(...) EXEC_ACTION_NO_CONCAT(M_FUNC, ##__VA_ARGS__)
    #define CALL_FUNC_NO_PASTING(...) EXEC_ACTION_NO_PASTING(M_FUNC, ##__VA_ARGS__)
    // expands to: "std::cout << \"foo, bar\" << std::endl;"
    STRINGIZE(M_EXPAND(CONCAT_(CALL_, FUNC(foo, bar))))