Search code examples
c++functionpolymorphismfactorystdany

C++ polymorphism factory How to build function object from its string name and map of parameter


#include <cassert>
#include <iostream>
#include <map>
#include <string>
#include <vector>
#include <any>
#include <functional>
#include <type_traits>
using namespace std;

using MapAny = map<string, any>;

class FunctionBase {
public:
    FunctionBase() {}
    FunctionBase(const MapAny &param_maps) {}
    virtual any operator()(const any &data) const = 0;
};

class Func1 : public FunctionBase {
private:
    int a,b;

public:
    Func1(const MapAny &param_map) {
        a = any_cast<int>(param_map.at("a"));
        b = any_cast<int>(param_map.at("b"));
    }

    virtual any operator()(const any &data) const override {        
        // calculate some F1(data, a, b)
        return a + b;
    }
};

class Func2 : public FunctionBase {
private:
    float c,d;    
public:
    Func2(const MapAny &param_map) {        
        c = any_cast<float>(param_map.at("c"));
        d = any_cast<float>(param_map.at("d"));
    }

    virtual any operator()(const any &data) const override {
        // calculate some F2(data, a, b)
        return c * d;
    }
};

FunctionBase* functionFactory(string name, const MapAny &param_map)
{
    if (name=="func1") return new Func1(param_map);
    if (name=="func2") return new Func2(param_map);
    return nullptr;
}

int main()
{
    // map<string, ???> functionMapping;
    // functionMapping["func1"] = ...
    // functionMapping["func2"] = ...   
    vector<float> data;

    MapAny param_map1;
    param_map1["a"] = 3;
    param_map1["b"] = 5;    
    FunctionBase* func1 = functionFactory("func1", param_map1); //functionMapping["func1"](param_map1);
    assert(any_cast<int>(func1->operator()(data))==8);

    MapAny param_map2;
    param_map2["c"] = 2.0f;
    param_map2["d"] = 4.0f;
    FunctionBase* func2 = functionFactory("func2", param_map2); //functionMapping["func2"](param_map2);
    assert(any_cast<float>(func2->operator()(data))==8.0);

    cout << "Correct\n";
}

I'm trying to make a Python-like C++ library, where user will give a map of string and parameters. The code will parse the map to functions, then execute the functions. Most importantly, I need to be able to write:

std::any param_map; // = something, doesn't matter here
FunctionBase *myfunc = new functionMapping["myfunc"](param_map);

Given the code in the example, what should be the value type of

map<string, ???> functionMapping;

Edit: added an equivalent solution using ugly if-statement


Solution

  • You may use an template based abstract factory. This is very generic and can be used for different types of keys and values.

    Maybe the following generic code gives you an idea, how you could implement your factory.

    #include <iostream>
    #include <map>
    #include <utility>
    #include <any>
    
    
    // Some demo classes ----------------------------------------------------------------------------------
    struct Base {
        Base(int d) : data(d) {};
        virtual ~Base() { std::cout << "Destructor Base\n"; }
        virtual void print() { std::cout << "Print Base\n"; }
        int data{};
    };
    struct Child1 : public Base {
        Child1(int d, std::string s) : Base(d) { std::cout << "Constructor Child1 " << d << " " << s << "\n"; }
        virtual ~Child1() { std::cout << "Destructor Child1\n"; }
        virtual void print() { std::cout << "Print Child1: " << data << "\n"; }
    };
    struct Child2 : public Base {
        Child2(int d, char c, long l) : Base(d) { std::cout << "Constructor Child2 " << d << " " << c << " " << l << "\n"; }
        virtual ~Child2() { std::cout << "Destructor Child2\n"; }
        virtual void print() { std::cout << "Print Child2: " << data << "\n"; }
    };
    struct Child3 : public Base {
        Child3(int d, long l, char c, std::string s) : Base(d) { std::cout << "Constructor Child3 " << d << " " << l << " " << c << " " << s << "\n"; }
        virtual ~Child3() { std::cout << "Destructor Child3\n"; }
        virtual void print() { std::cout << "Print Child3: " << data << "\n"; }
    };
    
    
    
    using UPTRB = std::unique_ptr<Base>;
    
    
    template <class Child, typename ...Args>
    UPTRB createClass(Args...args) { return std::make_unique<Child>(args...); }
    
    // The Factory ----------------------------------------------------------------------------------------
    template <class Key, class Object>
    class Factory
    {
        std::map<Key, std::any> selector;
    public:
        Factory() : selector() {}
        Factory(std::initializer_list<std::pair<const Key, std::any>> il) : selector(il) {}
    
        template<typename Function>
        void add(Key key, Function&& someFunction) { selector[key] = std::any(someFunction); };
    
        template <typename ... Args>
        Object create(Key key, Args ... args) {
            if (selector.find(key) != selector.end()) {
                return std::any_cast<std::add_pointer_t<Object(Args ...)>>(selector[key])(args...);
            }
            else return nullptr;
        }
    };
    
    int main()
    {
        // Define the factory with an initializer list
        Factory<int, UPTRB> factory{
            {1, createClass<Child1, int, std::string>},
            {2, createClass<Child2, int, char, long>}
        };
    
        // Add a new entry for the factory
        factory.add(3, createClass<Child3, int, long, char, std::string>);
    
    
        // Some test values
        std::string s1(" Hello1 "); std::string s3(" Hello3 ");
        int i = 1;  const int ci = 1;   int& ri = i;    const int& cri = i;   int&& rri = 1;
    
        UPTRB b1 = factory.create(1, 1, s1);
        UPTRB b2 = factory.create(2, 2, '2', 2L);
        UPTRB b3 = factory.create(3, 3, 3L, '3', s3);
    
        b1->print();
        b2->print();
        b3->print();
        b1 = factory.create(2, 4, '4', 4L);
        b1->print();
        return 0;
    }