How to use one map to store different function

Viewed 117

My colleague wrote a very long switch-case function as below:

void func(int type, int a, int b) {
    std::string str = "hello";
    switch (type) {
        case 1: {
            func1(a, b);
            break;
        }
        case 2: {
            func2(a, b, str);
            break;
        }
        // hundreds of cases...
    }
}

I want to change this terrible funciton because it has too many cases.

Good new is that each case only invokes one function, which must use a and b as its first and second parameters. Bad news is that some of them may need the third parameter.

I'm thinking if I can use a std::map<int, std::function<void(int, int, ...)>> to store all of functions in the cases but it's not working. It doesn't seem that std::function could accept a variadic function.

It there some other way to do so?

2 Answers

std::any will be your friend. Together with some wrapper class and a template function within the wrapper class, to hide the any cast, it will be some how more intuitive.

And it will give you additional possibilities.

Please see:

#include <iostream>
#include <map>
#include <string>
#include <any>
#include <utility>

class Caller
{
    std::map<int, std::any> selector;
public:
    Caller() : selector() {}

    Caller(std::initializer_list<std::pair<const int, std::any>> il) : selector(il) {}
    template<typename Function>
    void add(int key, Function&& someFunction) { selector[key] = std::any(someFunction); };

    template <typename ... Args>
    void call(int key, Args ... args) {
        if (selector.find(key) != selector.end()) {
            std::any_cast<std::add_pointer_t<void(Args ...)>>(selector[key])(args...);
        }
    }
};

// Some demo functions
void a(int x) { 
    std::cout << "a\t" << x << '\n'; 
};
void b(int x, int y) {
    std::cout << "b\t" << x << '\t' << y << '\n';
};
void c(int x, int y, std::string z) {
    std::cout << "c\t" << x << '\t' << y << '\t' << z << '\n';
};
void d(std::string s, int x, int y, int z) {
    std::cout << "d\t" << s << '\t' << x << '\t' << y << '\t' << z << '\n';
};


// Definition of our caller map (using initializer list)
Caller caller{
    {1, a},
    {2, b},
    {3, c} };

int main() {

    // Some demo calls
    caller.call(1, 1);
    caller.call(2, 1, 2);
    caller.call(3, 1, 2, std::string("3"));

    // Adding an additional function
    caller.add(4, d);

    // And call this as well.
    caller.call(4, std::string("ddd"), 1,2,3);
    return 0; 
}

You can wrap into lambdas like

void func(int type, int a, int b) {    
    std::string str = "hello";

    std::map<int, std::function<void(int, int)>> m {
        {1, [](int a, int b) { func1(a, b); } },
        {2, [str](int a, int b) { func2(a, b, str); } },
        ...
    };

    m[type](a, b);
}
Related