Как использовать одну карту для хранения разных функций

#c #c 11 #variadic-functions

Вопрос:

Мой коллега написал очень длинную функцию переключения, как показано ниже:

 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...
    }
}
 

Я хочу изменить эту ужасную функцию, потому что в ней слишком много случаев.

Хорошей новинкой является то, что в каждом случае вызывается только одна функция, которая должна использовать a и b в качестве своего первого и второго параметров. Плохая новость заключается в том, что некоторым из них может потребоваться третий параметр.

Я думаю, смогу ли я использовать a std::map<int, std::function<void(int, int, ...)>> для хранения всех функций в случаях, но это не работает. Кажется, что это не std::function может принять вариативную функцию.

Есть ли какой — то другой способ сделать это?

Комментарии:

1. Откуда берутся дополнительные аргументы? Для тех, кто может использовать лямбду для создания функции, занимающей два входа. Но в конечном счете большой переключатель будет более эффективным. Вы можете использовать массив указателей функций вместо карты.

2. @Бен, я переиздал свой вопрос. Все дополнительные аргументы определены в функции func .

3. Мне было более любопытно, какие функции имеют сколько дополнительных аргументов? Это всего лишь один или два или около того, большинство из которых требуют одного или нескольких дополнительных аргументов?

4. @Бен Есть только два вида функций: одна есть void(int, int) , другая есть void(int, int, ???) . На данный момент, ??? это всегда std::string так .

5. @Ben Я не могу точно сказать, сколько функций принимает два аргумента и сколько функций принимает три функции. Потому что это может измениться в любое время.

Ответ №1:

std::any будет твоим другом. Вместе с некоторым классом-оболочкой и функцией шаблона в классе-оболочке, чтобы скрыть любое приведение, это будет несколько более интуитивно понятным.

И это даст вам дополнительные возможности.

Пожалуйста, посмотрите:

 #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, Functionamp;amp; 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 << "at" << x << 'n'; 
};
void b(int x, int y) {
    std::cout << "bt" << x << 't' << y << 'n';
};
void c(int x, int y, std::string z) {
    std::cout << "ct" << x << 't' << y << 't' << z << 'n';
};
void d(std::string s, int x, int y, int z) {
    std::cout << "dt" << 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; 
}
 

Комментарии:

1. Кроме того, вы могли бы использовать std::map<int, std::variant<std::function<void(int, int)>, std::function<void(int, int, std::string)>>> .

Ответ №2:

Вы можете обернуть в лямбды, такие как

 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);
}
 

Комментарии:

1. Я переформулировал свой вопрос. Я думаю, подходит ли ваш метод для моего случая…

2. Ближе. Но, видите ли, я не хочу вводить так много кодов func . Я пытаюсь съехать std::map отсюда func . Я имею в виду, что я хочу определить std::map «вне func «, чтобы я мог просто писать такие вещи, как m[type](a, b) «внутри func «.

3. Если другого способа нет, я думаю , что мне могут понадобиться два map : один-для хранения функций с двумя параметрами, а другой-для хранения функций с тремя параметрами.

4. @Yves Я не могу придумать других способов сделать это, мы должны заранее указать подпись во время компиляции. И да, вы можете определить два map s, если тип 3-го параметра фиксирован.

5. Вы можете написать dummy_func1(int, int, str) , что просто игнорируете str вызовы и func1(int, int) с первыми двумя аргументами.