c++macrosc-preprocessormetaprogrammingexplicit-instantiation

Generate explicit instantiations with multiple parameters with preprocessor


In my project, I want to have a bunch of explicit instantiations of my templated functions to reduce build time. Now I have a lot of functions, which can have different templates. For this reason (and in case I want to have more of them) I do not want to type them manually, but have them generated by the preprocessor.

An example of what I want to have generated:

template bool match_any<x>();
template bool match_any<y<x>>();
template bool match_expr<x,y,z>();

whereas x is an integer between 1 and a defined max_dim, y can be one of three values: real, bool, index and z is either 0 or 1.

I want to generate now any possible combination of those three functions with the parameters (e.g. template bool match_any<0>(); template bool match_any<1>(); template bool match_any<2>(); ...), but not only specifically those, since I have ~100 of those functions with a similar structure.

Through Using macros to define forward declarations I have figured out how to do a repetitive pattern:

#define REP_INT_1(f) f(1)
#define REP_INT_2(f) REP_INT_1(f) f(2)
#define REP_INT_3(f) REP_INT_2(f) f(3)
#define REP_INT_4(f) REP_INT_3(f) f(4)

#define REP_INT(n,gen) REP_INT_(n,gen)
#define REP_INT_(n,gen) REP_INT_##n(gen)

which I can then use like

#define GEN(x) template bool match_any<x>();
REP_INT(3, GEN)
#undef GEN

Of course it is simple to repeat this pattern e.g. for strings.

But my problem is now, that because of the nature of the pattern (as I pass GEN as a 'function') I cannot of two arguments for GEN. Sure, I can altered the repetition pattern so it works also for two arguments, but then I would have to make a new pattern like this for any number of arguments and also for every order of them, which ultimatly kind of defeats the purpose, because I will have a big block for every function - then I can just write it manually.

Does anyone have an idea of to accomplish this with either a different way to 'loop' the possible arguments or how to extend my existing methods so it works for multiple arguments?


Solution

  • You can add to your macros like so to achieve a somewhat maintainable list:

    #define GEN_X(f) REP_INT(3, f)
    #define GEN_Y(f, x) f(x, real) f(x, bool) f(x, index)
    #define GEN_Z(f, x, y) f(x, y, 0) f(x, y, 1)
    
    // GEN_F3 = generate functions with at least 3 arguments
    #define GEN_F3(x, y, z) template bool match_any<x, y, z>();
    #define GEN_F2(x, y) template bool match_any<y<x>>(); GEN_Z(GEN_F3, x, y)
    #define GEN_F1(x) template bool match_any<x>(); GEN_Y(GEN_F2, x)
    GEN_X(GEN_F1)
    

    demo

    We separate our variable generators which we can then chain together to get all our permutations: Your macro generates the x's, which is piped into GEN_F1, which handles the single-argument case and pipes those x values to the y-generator, and so on.

    Be careful that although we've made the source code linearly maintainable, we can't avoid the exponential increase to the executable size.


    To address your extended question, if you wanted to be able to handle any permutation of numbers for two arguments, say for x={1,2,3}, y={1,2,3,4}, intuitively you may want to adjust like so:

    #define GEN_X(f) REP_INT(3, f)
    #define GEN_Y(f, x) REP_INT(4, f)
    

    This almost works, but macro expansion prevents using the same REP_INT_* macros again within the one recursive expansion (at least in the way that I've done it).

    One work-around is to have two REP_INT lists (and at least one of them needs to handle variadic inputs, appending the number to the end). The macros can be identical, except that the names need to differ to allow the expansion to continue.

    We can then solve the extended problem like so:

    #define GEN_X(f) REP1_INT_3(f)
    #define GEN_Y(f, x) REP2_INT_4(f, x)
    
    #define GEN_F2(x, y) template bool match_any<x, y>();
    #define GEN_F1(x) GEN_Y(GEN_F2, x)
    GEN_X(GEN_F1)
    

    demo