Search code examples
c++c++11templatesvariadic-templates

c++11 unpack std::tuple into virtual member function


The full story:

I'm trying to build a framework that looks a bit like this:

#include <tuple>
#include <memory>
using namespace std;

// this class allows user to call "run" without any args 
class simulation_base{
public:
    int run(){ execute_simulation_wrapped(); }; 
protected:
    virtual int execute_simulation_wrapped(); {return 0;};
}

// this class funnels some stored inputs into a soon-to-be-overridden method
template <typename Ts...>
class simulation_wrapper : public simulation_base {
    tuple<shared_ptr<Ts>... > stored_inputs;

    public:
    int execute_simulation_wrapped() {/* how do you call simulation method? */};

    protected:
    virtual int simulation(const Ts&...){return 0};
}

Now we can use the framework to define a couple of simple-looking classes that can be simulated..

class jones_household : public simulation_wrapper< woman, girl, cat >{
    int simulation(woman mrs_jones, girl mary, cat sniffles)
         // mrs_jones and her daugther mary play with sniffles the cat
         return 1;
    }
}

class smith_household : public simulation_wrapper< man, dog >{
    int simulation(man mr_smith, dog fido)
         // mr_smith and his dog fido go for a walk
         return 1;
    }
}

And then build a multiverse of these simulatable households...

smith_household uinverse_1_smiths;
smith_household uinverse_2_smiths;
jones_houshold uinverse_1_jones;
jones_houshold uinverse_2_jones;

// set the values of the stored_inputs (i.e. fido, sniffles etc.) 

Finally, we get to the point: we want to be able to write a function which is agnostic of household type, but is still able to call run on the simulation:

void play_simulation(simulation_base& some_household){
     // do some general stuff...

     some_household.run();  
}

In summary: run calls the relevant templated-instance of the virtual method execute_simulation_wrapped, which then unpacks the stored_inputs and provides them to the virtual simulation function which has a customized implementation for each household.


The question that I think I should be asking:

So, I think I've got most of the above set up right, but I've been looking at this for a long time and I still cant work out how the simulation_wrapper::execute_simulation_wrapped function can make the call to simulation and provide the unpacked tuple stored_inputs as a parameter pack.

I know there are SO questions and blogs out there that give details as to how to call a regular function with an unpacked tuple, but I haven't managed to extend this to member functions, and specifically virtual member functions.

TMP is new to me and still thoroughly confusing, so fairly explicit answers would be much appreciated!


Solution

  • This is usually done with a help of index_sequence:

    template <typename... Ts>
    class simulation_wrapper : public simulation_base
    {
        tuple<shared_ptr<Ts>... > stored_inputs{new Ts...};
    
    public:
    // MAGIC STARTS HERE
        int execute_simulation_wrapped() { return execute_simulation_wrapped(std::make_index_sequence<sizeof...(Ts)>{}); }
    
    private:
        template <std::size_t... Is>
        int execute_simulation_wrapped(std::index_sequence<Is...>) { return simulation(*std::get<Is>(stored_inputs)...); }
    // MAGIC ENDS HERE
    
    protected:
        virtual int simulation(const Ts&...){return 0;};
    };
    

    If you need an index_sequence, which is available in <utility> only since C++14, you can use the below implementation:

    template <std::size_t... Is>
    struct index_sequence {};
    
    template <std::size_t N, std::size_t... Is>
    struct make_index_sequence_h : make_index_sequence_h<N - 1, N - 1, Is...> {};
    
    template <std::size_t... Is>
    struct make_index_sequence_h<0, Is...>
    {
        using type = index_sequence<Is...>;
    };
    
    template <std::size_t N>
    using make_index_sequence = typename make_index_sequence_h<N>::type;
    

    DEMO