1

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++ :)

enanone
  • 923
  • 11
  • 25
  • Your example doesn't build. Those macros aren't replaced if the include order is as you specified. Or if the `#define`'s come second in general. – StoryTeller - Unslander Monica Apr 30 '18 at 12:20
  • 3
    Instead of making `Pol1..Poln` template parameters, you could make them type aliases inside of a single `typename PolicySet`. – HolyBlackCat Apr 30 '18 at 12:22
  • 2
    [OT]: Avoid including cpp file: `#include `, .hpp, .inl, .hxx would be better extension. – Jarod42 Apr 30 '18 at 12:29
  • 2
    `Shell MyShell();`: vexing parse: function declaration, you probably want `Shell MyShell{};` or `Shell MyShell;` instead. – Jarod42 Apr 30 '18 at 12:30
  • Hi @HolyBlackCat, what do you mean with the alias here? Do you mean something like the solution proposed by @Jarod42? Or more like the solution by @MaxLanghof? – enanone Apr 30 '18 at 14:20
  • By type alias I mean a `typedef` or a `using`. Something like @MaxLanghof's suggestion. – HolyBlackCat Apr 30 '18 at 15:13

2 Answers2

1

using alias seems better:

template <class int_t, class float_t>
using MyShell = Shell<int_t, float_t, MyPol1, MyPol2, ..., MyPolN>;
llllllllll
  • 16,169
  • 4
  • 31
  • 54
Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • I like this solution better than mine :) Still, this solution only works with macros right? – enanone Apr 30 '18 at 14:21
  • 1
    No need of MACRO, just define the alias once with concrete and expected policies. – Jarod42 Apr 30 '18 at 14:26
  • I see! Well, this is my approach right now, but I would like to save the user the need to write the long template argument list (second line in your "code"). I am trying to figure out how that can be done as a list of options (like the `default`s in my original post), rather than a template argument list (where, above all, order of teh arguments matters that much) – enanone Apr 30 '18 at 14:43
  • 1
    Writing the `using` code once, or MACRO list once seems similar from user point of view... – Jarod42 Apr 30 '18 at 14:47
  • Maybe you are right and my attempt to remove the "helper" (the alias) is an overkill. I should give a second thought to it :) – enanone Apr 30 '18 at 14:50
1

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.

Max Langhof
  • 23,383
  • 5
  • 39
  • 72
  • I really like using the `struct Config` to specify the options. In fact, I was looking for something like that --I just could not make it work. The ChangePolXTo is somewhat messy from a user point of view (just a noob talking here, though), but I definitely like this approach :) – enanone Apr 30 '18 at 14:26
  • 1
    I would move `ChangePol1To ` inside `Config` do have syntax similar to `DefaultConfig::WithPol1::WithPol3` which seems clearer. – Jarod42 Apr 30 '18 at 14:32
  • Btw, `using MyConfig1 = Config<>; using MyConfig2 = Config;` might be enough. – Jarod42 Apr 30 '18 at 14:35
  • @Jarod42 The "put it inside config" is a great idea, looks so much better! About your second suggestion: Changing the last policy when you have 10 of them would require you to repeat the 9 default ones first. I wanted to avoid that. – Max Langhof Apr 30 '18 at 14:39
  • I said *"might"*, so it depends of OP's usage. how many different configs OP has, how much they differ from each other, are they linked in some ways... – Jarod42 Apr 30 '18 at 14:44