1

I have this design:

class GenericData
{
};

class Data1 : public GenericData
{
};

class Data2 : public GenericData
{
};

class CompBase  
{
public:
    void process()
    {
        // inputs are check to make sure there number and order is correct
        // Use them to automatically call the correct function
        // What here ?
    }
    vector<GenericData> inputs;
};

class Comp1 : public CompBase
{
public:
    void compute(Data1 input1, Data1 input2) { cout << "Comp1::compute(Data1 input1, Data1 input2)" << endl; }
    void compute(Data2 input1, Data2 input2) { cout << "Comp1::compute(Data2 input1, Data2 input2)" << endl; }
    void compute(Data1 input1, Data2 input2) { cout << "Comp1::compute(Data1 input1, Data2 input2)" << endl; }
};

class Comp2 : public CompBase
{
public:
    void compute(Data1 input1) { cout << "Comp2::compute(Data1 input1)" << endl; }
    void compute(Data2 input1) { cout << "Comp2::compute(Data2 input1)" << endl; }
};

With the following constraints:

  • The compute functions must be called from GenericComp but can't be all declared here because there would be two many (Data1,2 and Comp1,2 are just examples)
  • I must be able to have a collection of CompBase
  • The compute functions must not have to check their inputs (i.e. passing them the same structure is not possible)
  • The code must be generic enough to allow addition of other Data, Comp and compute easily

Here is an example of use:

int main() {
    Data1 d1;   Data2 d2;
    Comp1 c1; Comp2 c2;

    c1.inputs = { d1, d1 };
    c1.process();           // "Comp1::compute(Data1 input1, Data1 input2)"

    c1.inputs = { d2, d2 };
    c1.process();           // "Comp1::compute(Data2 input1, Data2 input2)"

    c1.inputs = { d1, d2 };
    c1.process();           // "Comp1::compute(Data1 input1, Data2 input2)"

    vector<GenericComp> comps = { c1, c2 };
    for (comp : comps)
    {
        comp.process();
    }
    return 0;
}

I have a "working" example of this here.

I tried different approaches: CRTP, variadic template functions, currying and partial application and a lot of googling but I'm stuck here.

Is it possible with these constraints ? If so how could you do that ?

Nauss
  • 11
  • 3
  • Hi, Welcome to StackOverflow - why do you think it **won't** be possible with those constraints? – ScottMcGready Sep 04 '15 at 19:38
  • `vector` can't store a mix of objects, it will slice and only (copies of) the base class portions will end up in the vector. Polymorphism requires pointers or references, so try `vector>` Mutatis mutandi for `vector` – Ben Voigt Sep 04 '15 at 19:41
  • @ScottMcGready because I tried many ways and I feel compile time needs run-time information... – Nauss Sep 04 '15 at 19:43
  • @BenVoigt the final code will of course use pointers, didn't want to bother with for the explanation – Nauss Sep 04 '15 at 19:45
  • Intentionally asking a question different from the one you care about doesn't lead to useful answers. In particular, shortcuts taken in the English explanation should be fully written out in the code, especially in the "working example" offsite link. – Ben Voigt Sep 04 '15 at 19:48
  • Would that be ok with simple pointers: vector ? – Nauss Sep 04 '15 at 19:55
  • That won't (obviously) work completely at compile time, you're going to have some runtime overhead. What you basically seem to want is implementing a dynamic function call. I wrote something like this here: http://stackoverflow.com/a/32041381/1116364 – Daniel Jour Sep 04 '15 at 20:50
  • Very interesting, I'll have look and give it a try and come back here. Thanks – Nauss Sep 08 '15 at 09:09

1 Answers1

0

Thank you guys for the answers. @Daniel Jour, your post really helped me and I had few modifications to make to fit my case.

Here is an updated example that will work for me.

#include <iostream>
#include <vector>
#include <map>
#include <functional>
#include <memory>
using namespace std;

class GenericData
{
public:
    virtual ~GenericData() {};
};

class Data1 : public GenericData
{
public:
    virtual ~Data1() {};
};

class Data2 : public GenericData
{
public:
    virtual ~Data2() {};
};

class GenericComp
{
public:
    virtual ~GenericComp() {};
    vector<GenericData*> inputs;
};

class Comp1 : public GenericComp
{
public:
    static bool compute(shared_ptr<Data1> const & input1, shared_ptr<Data1> const & input2) { cout << "Comp1::compute(Data1 input1, Data1 input2)" << (input2 ? "ok" : "null") << endl; return true; }
    static bool compute(shared_ptr<Data2> const & input1, shared_ptr<Data2> const & input2) { cout << "Comp1::compute(Data2 input1, Data2 input2)" << endl; return true; }
    static bool compute(shared_ptr<Data1> const & input1, shared_ptr<Data2> const & input2) { cout << "Comp1::compute(Data1 input1, Data2 input2)" << endl; return true; }
};

class Comp2 : public GenericComp
{
public:
    static bool compute(shared_ptr<Data1> const & input1) { cout << "Comp2::compute(Data1 input1)" << endl; return true; }
    static bool compute(shared_ptr<Data2> const & input1) { cout << "Comp2::compute(Data2 input1)" << endl; return true; }
};

// Arguments type to the function "interface"
using Arguments = std::vector<shared_ptr<GenericData>> const &;
// the interface
using Function = std::function<bool (Arguments)>;

// Base case of packing a function.
// If it's taking a vector and no more
// arguments, then there's nothing left to
// pack.
template<std::size_t N, typename Fn>
Function pack(Fn && fn)
{
    return [fn = std::forward<decltype(fn)>(fn)] (Arguments arguments)
    {
        if (N != arguments.size())
        {
            throw std::string{"wrong number of arguments, expected "} +
              std::to_string(N) +
              std::string{" but got "} +
              std::to_string(arguments.size());
        }
        return fn(arguments);
    };
}

// pack a function to a function that takes
// it's arguments from a vector, one argument after
// the other.
template<std::size_t N, typename Arg, typename... Args, typename Fn>
Function pack(Fn && fn)
{
    return pack<N+1, Args...>([fn = std::forward<decltype(fn)>(fn)] (Arguments arguments, Args const &... args)
    {
        try
        {
            return fn(arguments, arguments.at(N), args...);
        }
        catch (std::bad_cast const &)
        {
            throw std::string{"argument "} + std::to_string(N) + std::string{" has wrong type "};
        }
    });
}

// transform a function into one that takes its
// arguments from a vector
template<typename... Args, typename Fn>
Function pack_function(Fn && fn)
{
    return pack<0, Args...>([fn = std::forward<decltype(fn)>(fn)] (Arguments arguments, Args const &... args) -> bool
    {
        return fn(args...);
    });
}

int main() {
    // Pack all the functions
    std::map<std::string, Function> operations;
    operations["Comp1_Data1_Data1"] = pack_function<shared_ptr<GenericData>, shared_ptr<GenericData>>([] (shared_ptr<GenericData> const & i1, shared_ptr<GenericData> const & i2)
    {
        return Comp1::compute(dynamic_pointer_cast<Data1>(i1), dynamic_pointer_cast<Data1>(i2));
    });
    operations["Comp1_Data2_Data2"] = pack_function<shared_ptr<GenericData>, shared_ptr<GenericData>>([] (shared_ptr<GenericData> const & i1, shared_ptr<GenericData> const & i2)
    {
        return Comp1::compute(dynamic_pointer_cast<Data2>(i1), dynamic_pointer_cast<Data2>(i2));
    });
    operations["Comp1_Data1_Data2"] = pack_function<shared_ptr<GenericData>, shared_ptr<GenericData>>([] (shared_ptr<GenericData> const & i1, shared_ptr<GenericData> const & i2)
    {
        return Comp1::compute(dynamic_pointer_cast<Data1>(i1), dynamic_pointer_cast<Data2>(i2));
    });
    operations["Comp2_Data1"] = pack_function<shared_ptr<GenericData>>([] (shared_ptr<GenericData> const & i1)
    {
        return Comp2::compute(dynamic_pointer_cast<Data1>(i1));
    });
    operations["Comp2_Data2"] = pack_function<shared_ptr<GenericData>>([] (shared_ptr<GenericData> const & i1)
    {
        return Comp2::compute(dynamic_pointer_cast<Data2>(i1));
    });

    // Create the possible inputs
    vector<shared_ptr<GenericData>> data1_data1 { shared_ptr<Data1>(), shared_ptr<Data1>() };
    vector<shared_ptr<GenericData>> data2_data2 { shared_ptr<Data2>(), shared_ptr<Data2>() };
    vector<shared_ptr<GenericData>> data1_data2 { shared_ptr<Data1>(), shared_ptr<Data2>() };
    vector<shared_ptr<GenericData>> data1 { shared_ptr<Data1>() };
    vector<shared_ptr<GenericData>> data2 { shared_ptr<Data2>() };

    // The calls !
    operations["Comp1_Data1_Data1"](data1_data1);
    operations["Comp1_Data2_Data2"](data2_data2);
    operations["Comp1_Data1_Data2"](data1_data2);
    operations["Comp2_Data1"](data1);
    operations["Comp2_Data2"](data2);

    // Wrong arguments
    try
    {
        operations["Comp1_Data1_Data1"](data1);
    }
    catch (std::string const & e)
    {
        cout << e << endl;
    }
    try
    {
        operations["Comp2_Data1"](data1_data1);
    }
    catch (std::string const & e)
    {
        cout << e << endl;
    }
    return 0;
}
Nauss
  • 11
  • 3