Search code examples
c++c++11boostc-preprocessorboost-preprocessor

Variadic macros: Reuse variadic arguments (Boost.Fusion)


I am working on a kind of "data-variable" framework. Here, structs are defined and adapted as Boost.Fusion sequences using the macro BOOST_FUSION_DEFINE_ASSOC_STRUCT.

Brief context:

To define two adapted structs foo and bar, I use:

#define VAR(VARNAME) vardefs::VARNAME
#define VAR_PARENTHESES(VARNAME) (vardefs)(VARNAME)

// ========================================================
#ifndef VARNAME_
#define VARNAME_ foo // <- Variable name
namespace VAR(VARNAME_)::keys{
    struct foo1;
    struct foo2;
    struct foo3;
}
namespace VAR(VARNAME_) { struct index; }

BOOST_FUSION_DEFINE_ASSOC_STRUCT(
    VAR_PARENTHESES(VARNAME_), type,
    (char, foo1, VAR(VARNAME_)::keys::foo1)
    (int, foo2, VAR(VARNAME_)::keys::foo2)
    (float, foo3, VAR(VARNAME_)::keys::foo3)
)
#endif
#undef VARNAME_
// --------------------------------------------------------

// ========================================================
#ifndef VARNAME_
#define VARNAME_ bar // <- Variable name
namespace VAR(VARNAME_)::keys{
    struct bar1;
    struct bar2;
}
namespace VAR(VARNAME_) { struct index; }

BOOST_FUSION_DEFINE_ASSOC_STRUCT(
    VAR_PARENTHESES(VARNAME_), type,
    (double, bar1, VAR(VARNAME_)::keys::bar1)
    (float, bar2, VAR(VARNAME_)::keys::bar2)
)
#endif
#undef VARNAME_
// --------------------------------------------------------

Now one can create instances of foo and bar by using:

VAR(foo)::type fooI;
VAR(bar)::type barI;

The associative keys can be used like this:

auto a = boost::fusion::at_key<VAR(foo)::keys::foo3>(fooI).
auto b = boost::fusion::at_key<VAR(bar)::keys::bar2>(barI).

This approach might be useful down the line.

Lastly, there are tags for the structs themselves:

VAR(bar)::index
VAR(foo)::index

I can use the later as type-keys in other Boost.Fusion sequences, e.g. boost::fusion::map.

Question:

Notice that, for each of the two structs, and since I am defining an associative sequence, I have to use the struct-field names (foo1, bar2, ...) three times each.

What I would like instead, is to define the above two structs as this:

// ========================================================
DEFINE_DATAVAR_STRUCT(
    foo,
    (char, foo1)
    (int, foo2)
    (float, foo3)
)
// --------------------------------------------------------

// ========================================================
DEFINE_DATAVAR_STRUCT(
    bar,
    (double, bar1)
    (float, bar2)
)
// --------------------------------------------------------

I only need to define the variadic macro DEFINE_DATAVAR_STRUCT. This is what I need help for. The problem for me, is to reuse variadic arguments so that they can appear in more than place in the generated code. Somehow, Boost.Fusion is doing this with no problem somehow (by relying on Boost.PP).

My own research:

I have looked at the Boost.PP library, but I am not doing very good progress here. Since the Boost.Fusion library is doing this already, there must be a way to achieve the same variadic functionality, similar to the BOOST_FUSION_DEFINE_ASSOC_STRUCT macro.

The Boost.Fusion macro BOOST_FUSION_DEFINE_ASSOC_STRUCT is defined on lines 40-50 in .../define_assoc_struct.hpp. It seems much of the magic to build these macros is to be found in .../define_struct.hpp (especially the macro BOOST_FUSION_DEFINE_STRUCT_IMPL on line 413). This file also takes advantage of a lot of Boost.PP.

Compiler: Microsoft Visual Studio 2015 Update 3 (GCC, Clang are not options right now).

Boost: 1.64.0.


Solution

  • It is relatively easy to construct a macro that generates all your boilerplate from an invocation of your preferred macro:

    DEFINE_DATAVAR_STRUCT(
        bar,
        (double, bar1)
        (float, bar2)
    )
    

    In this macro you have two arguments: a name (bar) and a "sequence" of tuples ((double,bar1)(float,bar2)). I put "sequence" in quotes because in order to have a sequence of tuples that can work with Boost.Preprocessor macros you need to have each tuple delimited by 2 sets of parentheses. In the example below most of the complexity is caused by the solution to this problem (there is more information about this in approach 3 in this answer.


    Running on Wandbox

    #include <iostream>
    #include <boost/preprocessor/seq/for_each.hpp>
    #include <boost/preprocessor/tuple/elem.hpp>
    #include <boost/preprocessor/cat.hpp>
    #include <boost/fusion/include/define_assoc_struct.hpp>
    #include <boost/fusion/include/at_key.hpp>
    
    
    
    #define VAR(VARNAME) vardefs::VARNAME
    #define VAR_PARENTHESES(VARNAME) (vardefs)(VARNAME)
    
    //THIS IS ONLY NEEDED IN ORDER TO GET 2 SETS OF PARENTHESES
    //Heavily "inspired" from BOOST_FUSION_ADAPT_STRUCT
    #define GENERATE_DATAVAR_SEQUENCE_FILLER_0(X, Y)  \
        ((X, Y)) GENERATE_DATAVAR_SEQUENCE_FILLER_1
    #define GENERATE_DATAVAR_SEQUENCE_FILLER_1(X, Y)  \
        ((X, Y)) GENERATE_DATAVAR_SEQUENCE_FILLER_0
    #define GENERATE_DATAVAR_SEQUENCE_FILLER_0_END
    #define GENERATE_DATAVAR_SEQUENCE_FILLER_1_END
    
    #define GENERATE_DATAVAR_SEQUENCE(MEMBERS) BOOST_PP_CAT(GENERATE_DATAVAR_SEQUENCE_FILLER_0 MEMBERS,_END)
    
    //THESE AREN'T ACTUALLY REQUIRED BUT HELP WITH READABILITY
    #define DATAVAR_GET_TYPE(TUPLE) BOOST_PP_TUPLE_ELEM(2,0,TUPLE)
    #define DATAVAR_GET_NAME(TUPLE) BOOST_PP_TUPLE_ELEM(2,1,TUPLE)
    
    //THESE ARE THE HELPERS THAT ACTUALLY GENERATE THE VARIABLE PARTS OF THE MACRO
    #define GENERATE_STRUCT_KEYS(_,__,TUPLE) struct DATAVAR_GET_NAME(TUPLE);
    #define GENERATE_STRUCT_DEFINITION_SEQ(_,NAME,TUPLE) (DATAVAR_GET_TYPE(TUPLE),DATAVAR_GET_NAME(TUPLE),VAR(NAME)::keys::DATAVAR_GET_NAME(TUPLE))
    
    // ===============================================================================================
    #define DEFINE_DATAVAR_STRUCT(NAME,MEMBERS)                                                      \
    namespace VAR(NAME)::keys{                                                                       \
        BOOST_PP_SEQ_FOR_EACH(GENERATE_STRUCT_KEYS,_,GENERATE_DATAVAR_SEQUENCE(MEMBERS))             \
    }                                                                                                \
    namespace VAR(NAME) { struct index; }                                                            \
    BOOST_FUSION_DEFINE_ASSOC_STRUCT(                                                                \
        VAR_PARENTHESES(NAME), type,                                                                 \
        BOOST_PP_SEQ_FOR_EACH(GENERATE_STRUCT_DEFINITION_SEQ,NAME,GENERATE_DATAVAR_SEQUENCE(MEMBERS))\
    )
    // -----------------------------------------------------------------------------------------------
    
    
    // ========================================================
    DEFINE_DATAVAR_STRUCT(
        foo,
        (char, foo1)
        (int, foo2)
        (float, foo3)
    )
    // --------------------------------------------------------
    
    // ========================================================
    DEFINE_DATAVAR_STRUCT(
        bar,
        (double, bar1)
        (float, bar2)
    )
    // --------------------------------------------------------
    
    
    int main()
    {
        VAR(foo)::type fooI{'a',1,2.0f};
        VAR(bar)::type barI{1.0,2.0f};
    
        std::cout << boost::fusion::at_key<VAR(foo)::keys::foo1>(fooI) << std::endl;
        std::cout << boost::fusion::at_key<VAR(foo)::keys::foo2>(fooI) << std::endl;
        std::cout << boost::fusion::at_key<VAR(foo)::keys::foo3>(fooI) << std::endl;
        std::cout << boost::fusion::at_key<VAR(bar)::keys::bar1>(barI) << std::endl;
        std::cout << boost::fusion::at_key<VAR(bar)::keys::bar2>(barI) << std::endl;
    }