Сохранять функции с различными сигнатурами на карте

Я пытаюсь создать ключ map с string в качестве ключа и общий метод как value в С++, но я не знаю, возможно ли это. Я хотел бы сделать что-то вроде этого:

void foo(int x, int y)
{
   //do something
}

void bar(std::string x, int y, int z)
{
   //do something
} 

void main()
{
   std::map<std::string, "Any Method"> map;

   map["foo"] = &foo;      //store the methods in the map
   map["bar"] = &bar;

   map["foo"](1, 2);       //call them with parameters I get at runtime
   map["bar"]("Hello", 1, 2);
}

Это возможно? Если да, то как я могу это понять?

Ответ 1

Вы можете ввести типы стилей в контейнер, а затем предоставить шаблон operator(). Это вызовет std::bad_any_cast, если вы ошибетесь.

template<typename Ret>
struct AnyCallable
{
    template<typename ... Args>
    AnyCallable(std::function<Ret(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    Ret operator()(Args && ... args) { return std::any_cast<std::function<Ret(Args...)>>(m_any)(std::forward<Args...>(args...)); }
    std::any m_any;
}

template<>
struct AnyCallable<void>
{
    template<typename ... Args>
    AnyCallable(std::function<void(Args...)> fun) : m_any(fun) {}
    template<typename ... Args>
    void operator()(Args && ... args) { std::any_cast<std::function<void(Args...)>>(m_any)(std::forward<Args...>(args...)); }
    std::any m_any;
}

void foo(int x, int y)
{
   //do something
}

void bar(std::string x, int y, int z)
{
   //do something
} 

void main()
{
   std::map<std::string, AnyCallable<void>> map;

   map["foo"] = &foo;      //store the methods in the map
   map["bar"] = &bar;

   map["foo"](1, 2);       //call them with parameters I get at runtime
   map["bar"]("Hello", 1, 2);
}

Ответ 2

В большинстве случаев (я не могу сказать лучше всего) вы можете использовать стирание подписи. Это означает преобразовать указатель на функции в общий тип подписи и затем преобразовать их обратно в правильную подпись перед их использованием.

Это можно сделать только в особых случаях (я не могу представить себе реальный мир) и будет очень небезопасным: ничто не мешает вам передать неправильные параметры функции. Короче: НИКОГДА НЕ ДЕЛАЙТЕ ЭТО В РЕАЛЬНОМ КОДЕ МИРА.

Как говорится, вот рабочий пример:

#include <iostream>
#include <string>
#include <map>

typedef void (*voidfunc)();

void foo(int x, int y)
{
    std::cout << "foo " << x << " " << y << std::endl;
}

void bar(std::string x, int y, int z)
{
    std::cout << "bar " << x << " " << y << " " << z << std::endl;
}

int main()
{
    std::map<std::string, voidfunc> m;
    m["foo"] = (voidfunc) &foo;
    m["bar"] = (voidfunc)& bar;
    ((void(*)(int, int)) m["foo"])(1, 2);
    ((void(*)(std::string, int, int)) m["bar"])("baz", 1, 2);
    return 0;
}

Он дает как ожидалось:

foo 1 2
bar baz 1 2

Я не мог найти в стандарте, вызывает ли это вызов или нет Undefined Behavior, потому что мало говорится о преобразованиях указателей функций, но я уверен, что все распространенные компиляторы принимают это, потому что он включает в себя только кавычки указателей функций.

Ответ 3

Вы не можете хранить функции с разными сигнатурами в контейнере типа map, независимо от того, сохраните ли вы их как указатель на функцию или std ::function<WHATEVER>. Информация о сигнатуре функции является одной и единственной в обоих случаях.

Типы для value in map - это один, что означает, что сохраненный в нем объект все одного и того же типа.

Итак, если ваши функции имеют одинаковую подпись, тогда это легко, иначе вам придется отказаться от безопасности типов и начать ходить в очень опасном мире. Тот, в котором вы стираете информацию о типе о функциях, хранящихся внутри карты. Это означает что-то вроде map<string, void*>.