1

I am running simulations that require that I use templates for an int parameter (D = the dimension of my systems). A typical simulation function is

template <int D> void simulation();

And when I want to specialize this template, I use a switch

switch(d){
case 2:
    simulation<2>();
    break;
case 3:
    simulation<3>();
    break;
// etc.
}

As far as I have one simulation function, it is OK. But imagine I have 10 of then (simul1, simul2,… simul10), and d can go from 2 to 10. I have to write ten time the same switch!

I was wondering if it were possible to factorize it, and have something like:

template <void (*fun)()> runSimulation(int d){
    switch(d){
    case 2:
        fun<2>();
    }
}

Of course <void (*fun)()> doesn't do what I want, since fun is a template<int>. Is there a way to do it?

Praetorian
  • 106,671
  • 19
  • 240
  • 328
styko
  • 641
  • 3
  • 14
  • One option would be to use Boost.Preprocessor to generate the `case` statements. – Praetorian Jun 25 '15 at 23:00
  • Alternatively, you could pass a `tuple` of ints, and use some metaprogramming to have the comiler iterate (= recurse) through the equivalent of your hand-written cases. – Ami Tavory Jun 25 '15 at 23:01
  • Would it be possible to change the simulation functions to classes? Like: `template struct simulation { static void apply(); };`? Or even `struct simulation { template static void apply(); };`? – Daniel Frey Jun 25 '15 at 23:07
  • Why not? Are you suggesting, I should use inheritance from an abstract class `Simul`? And then `void runSimul(Simul& s, int d)`. Or even more simple? – styko Jun 25 '15 at 23:22
  • @styko Way more simple, see my answer. – Daniel Frey Jun 25 '15 at 23:30

3 Answers3

2

When you can change the simulation function to a class with a static method:

struct sim1
{
    template<int D> static void apply();
};

The following should then work:

template <typename Sim> runSimulation(int d){
    switch(d){
    case 2:
        Sim::template apply<2>();
    case 3:
        Sim::template apply<3>();
    // ...
    }
}

Which is generic and can be called with runSimulation<sim1>(d); or runSimulation<sim2>(d); etc.

Daniel Frey
  • 55,810
  • 13
  • 122
  • 180
  • could you please explain the principle here? ... because to me it seems as if one still needs to manually write the switch-statement. – davidhigh Jun 26 '15 at 00:16
  • @davidhigh Yes, you need to write out the switch statement - but only once. The question was about how to avoid writing the same long switch statement for each `sim1`, `sim2`, etc. - this is avoided by the above. – Daniel Frey Jun 26 '15 at 00:18
  • ok, correct. I got it wrong in my answer and reduced the single switch-cases instead. – davidhigh Jun 26 '15 at 00:28
0

Here is a simple scheme to turn runtime information into compile time information using template recursion:

template<int N> void runSimulation() { std::cout<<"runSimulation " << N << std::endl; }

constexpr size_t max_index = 100;

namespace detail
{
   //overload for maximum index
    void callSimulation_impl(size_t i, std::integral_constant<size_t, max_index>) {}

    template<size_t N>
    void callSimulation_impl(size_t i, std::integral_constant<size_t, N>)
    {
        if(i==N)
        {
            runSimulation<N>();
        }
        else
        {
            callSimulation_impl(i, std::integral_constant<size_t, N+1>());
        }
    }
}

void callSimulation(size_t i)
{
    detail::callSimulation_impl(i, std::integral_constant<size_t, 0>());
}

int main(int argc, char *argv[])
{
    callSimulation(10);                  // calls runSimulation<10>();

    //or also:

    callSimulation(rand()%max_index);    //calls a random simulation
}

DEMO

Just as the manual switch you generated, it requires an effort linear in the size of the passed index (a similar algorithmic implementation is also possible using a binary search).

If efficiency matters, you can also arrive at an O(1) effort by using a magic switch, see here and here -- I'm a big fan of it.


EDIT: this scheme can be combined with the other answer by @Daniel Frey in order to arrive at a single function for multiple simulation types and arbitrarily many switch-cases.

Community
  • 1
  • 1
davidhigh
  • 14,652
  • 2
  • 44
  • 75
0

It would be great if C++ would accept template functions as template arguments, but I don't know how to express that. However, it accepts template classes as template arguments.

If you are willing to wrap your families of simulations (simul1, simul2, and so on) into one wrapper template per family (Wrapper1, Wrapper2, ...) you can do the following:

template<template<int D> class SimulationFunctionWrapper> struct Caller {
    static void simulation(int d) {
        switch(d) {
            case 2: SimulationFunctionWrapper<2>::run(); break;
            case 3: SimulationFunctionWrapper<3>::run(); break;
        }
    }
};

#include <iostream>

// normal simul1 declarations and definitions
template<int D> void simul1();
template<> void simul1<2>() { std::cout << "simul1<2>\n"; }
template<> void simul1<3>() { std::cout << "simul1<3>\n"; }

// Enables dispatching to the right simul1 based on the template integer
template<int D> struct Wrapper1 { static void run() { simul1<D>(); } };

// normal simul2 declarations and definitions
template<int D> void simul2();
template<> void simul2<2>() { std::cout << "simul2<2>\n"; }
template<> void simul2<3>() { std::cout << "simul2<3>\n"; }

// Enables dispatching to the right simul2 based on the template integer
template<int D> struct Wrapper2 { static void run() { simul2<D>(); } };

int main(int argc, const char *argv[]) {
    Caller<Wrapper1>::simulation(argc);
    Caller<Wrapper2>::simulation(argc);
    return 0;
}

This shows how you can decide with the same code (Caller) which one of the individual functions to call. Unfortunately, it requires the boilerplate Wrapper.

TheCppZoo
  • 1,219
  • 7
  • 12