Search code examples
c++c++11templatesstd-function

Is there a way to call a function with different number of variables?


I would like to design a way to call one of many very similar functions - where the only difference is the number of arguments. Bare in mind this is a minimal example of my larger issue. I have lots of functions for doing different tasks. Each function prototype is very similar - except they have a different number of parameters.

I want to be able to call some wrapper class that I tell it the test type and pass it a list (vector) of params and it sorts the rest out for me.

Here is a working setup (with the parts that don't work commented out):

#include <vector>
#include <map>
#include <functional>
#include <iostream>

enum class test_type {t0, t1, t2, t3, t4, tn};

class tester
{
public:
    void run(test_type type, const std::vector<int> &args)
    {
        func_map[type].func();
    };

private:    
    void function_0()                       { std::cout << "f0\n"; };
    void function_1(int p1, int p2)         { std::cout << "f1\n"; };
    void function_2(int p1, int p2, int p3) { std::cout << "f2\n"; };
    void function_3(int p1, int p2)         { std::cout << "f3\n"; };
    void function_4(int p1)                 { std::cout << "f4\n"; };
    void function_n(int p1)                 { std::cout << "fn\n"; };


    struct test_info
    {
        std::function<void()> func; // variadic function std::function<void(...)> ?
        int num_params;
    };

    std::map<test_type, test_info> func_map {
        {test_type::t0, {[this]{function_0();}, 0}}
        // {test_type::t1, {[this]{function_1();}, 2}}
        // {test_type::t2, {[this]{function_2();}, 3}}
        // {test_type::t3, {[this]{function_3();}, 2}}
        // {test_type::t4, {[this]{function_4();}, 1}}
        // {test_type::tn, {[this]{function_n();}, 1}}
    };
};

int main() {

    tester test;
    test.run(test_type::t0, {});
    //test.run(test_type::t1, {1, 2});
    //test.run(test_type::t2, {1, 2, 3});
    //test.run(test_type::t3, {1, 2});
    //test.run(test_type::t4, {1});
    //test.run(test_type::tn, {1});

    return 0;
}

so this works since function_0 has 0 parameters and therefore matches std::function<void()> type. I am not really sure what the best direction to take from here is.

I think there is an option to use variadic std::function<void(...)> but reading up on this took me beyond my confort zone and my understanding of variadic templating is not so great.

So my questions are:

  • Can this be done with varidic std::funtion<void(...)> in some way?
  • Is there a better way to achieve this?

Solution

  • Pass the vector itself to the wrapper lambdas, and let them extract the int values to pass along to their target functions, eg:

    #include <vector>
    #include <map>
    #include <functional>
    #include <iostream>
    #include <stdexcept>
    
    enum class test_type {t0, t1, t2, t3, t4, tn};
    
    class tester
    {
    public:
        void run(test_type type, const std::vector<int> &args)
        {
            func_map[type](args);
        };
    
    private:    
        void function_0()                       { std::cout << "f0\n"; };
        void function_1(int p1, int p2)         { std::cout << "f1\n"; };
        void function_2(int p1, int p2, int p3) { std::cout << "f2\n"; };
        void function_3(int p1, int p2)         { std::cout << "f3\n"; };
        void function_4(int p1)                 { std::cout << "f4\n"; };
        void function_n(int p1)                 { std::cout << "fn\n"; };
    
        static void validate(const std::vector<int> &args, size_t needed) {
            if (args.size() != needed)
                throw std::invalid_argument("wrong number of arguments");
        }
    
        using func_type = std::function<void(const std::vector<int> &)>;
    
        std::map<test_type, func_type> func_map {
            {test_type::t0,
              [this](const std::vector<int> &args){
                validate(args, 0);
                function_0();
              }
            },
            {test_type::t1,
              [this](const std::vector<int> &args){
                validate(args, 2);
                function_1(args[0], args[1]);
              }
            },
            {test_type::t2,
              [this](const std::vector<int> &args){
                validate(args, 3);
                function_2(args[0], args[1], args[2]);
              }
            },
            {test_type::t3,
              [this](const std::vector<int> &args){
                validate(args, 2);
                function_3(args[0], args[1]);
              }
            },
            {test_type::t4,
              [this](const std::vector<int> &args){
                validate(args, 1);
                function_4(args[0]);
              }
            },
            {test_type::tn,
              [this](const std::vector<int> &args){
                validate(args, 1);
                function_n(args[0]);
              }
            },
        };
    };
    
    int main() {
    
        tester test;
        test.run(test_type::t0, {});
        test.run(test_type::t1, {1, 2});
        test.run(test_type::t2, {1, 2, 3});
        test.run(test_type::t3, {1, 2});
        test.run(test_type::t4, {1});
        test.run(test_type::tn, {1});
    
        return 0;
    }
    

    Live Demo