c++templatesmacrospolicy-based-design

Convenienve of macros in case of a large template argument list


In my design, I have a class (let it be Shell) that is templated against a large number of parameters. Most of those parameters are other classes that I define within my project, since I have chosen a Policy-Based-Design approach for my project.

The class declaration looks something like this:

template <class int_t, class float_t, class Pol1, class Pol2,..., class PolN>
class Shell : Pol1, Pol2,...,PolN

The two first arguments are the integer and floating types that shall be used. The remaining paramenters are specific policies definied within the project.

This type of design is convenient for me, since it allows to avoid a lot of run-time checks (and we are targeting run-time performance). However, it is very messy (from a user perspective) to type a list of 10+ arguments whenever he/she wants to create an instance of the Shell class.

For this reason, I have chosen to move this typing burden to a separate file, along with macros. First, I default all the policies to a macro:

template <class int_t, class float_t, class Pol1 = DEF_1, class Pol2 = DEF_2,..., class PolN = DEF_N>
class Shell : Pol1, Pol2,...,PolN

And the template parameters can be provided in a separate files, as macros, instead of in the declaration of the solver. For example:

#include <shell.cpp>

#define DEF_1 example1
#define DEF_2 example2
...
#define DEF_N exampleN

int main(){
    Shell<int,double> MyShell();
    return 0;
    }

This way, instantiating the class only needs passing to template parameters (the other +10 parameters are passed via the macros). The define's could be even moved to a separate file, as in:

#include <shell.cpp>
#include "define.hpp"

This is just a workaround, so that one does not have to provide a 10+ parameters argument list everytime you create an instance of the class. Macros are the best solution I have found so far. However, I know that macros are not a "recommended" solution in most C++ applications.

For this reason, I would like to know if this is a typical problem, and how can you overcome it without macros. I would also like to know if macros are an "ok" solution, or if I should avoid this design at all cost. I would appreciate any help/comment on this topic, since I am quite new to C++ :)


Solution

  • Here is a solution that is entirely macro-free, allows defaults and individual changes of policies from the default (without having to re-specify the defaults) and should scale with little effort:

    struct DefaultPol1 {};
    struct DefaultPol2 {};
    struct DefaultPol3 {};
    
    struct OtherPol1 {};
    struct OtherPol2 {};
    struct OtherPol3 {};
    
    template<class Pol1 = DefaultPol1, class Pol2 = DefaultPol2, class Pol3 = DefaultPol3>
    struct Config
    {
        using P1 = Pol1;
        using P2 = Pol2;
        using P3 = Pol3;
    
    
        template <class NewPol1>
        using ChangePol1To = Config<NewPol1, Pol2, Pol3>;
    
        template <class NewPol2>
        using ChangePol2To = Config<Pol1, NewPol2, Pol3>;
    
        template <class NewPol3>
        using ChangePol3To = Config<Pol1, Pol2, NewPol3>;
    };
    
    using DefaultConfig = Config<>;
    
    
    template <class int_t, class float_t, class C = DefaultConfig>
    class Shell : C::P1, C::P2, C::P3
    {};
    
    
    void foo()
    {
        using config = DefaultConfig::ChangePol3To<OtherPol3>::ChangePol1To<OtherPol1>;
        Shell<unsigned, double, config> myShell;
    }
    

    https://godbolt.org/g/uPrRhc (improved version, thanks to @Jarod42!)
    (If you change the empty struct definitions to be only (forward) declarations, the compiler errors show that the right policies are selected).

    Naming could probably improved with more context.