Как хранить функциональные объекты с разными подписями в контейнере? - PullRequest
3 голосов
/ 29 ноября 2011

Итак, представьте, что у нас было 2 функции (void : ( void ) ) и (std::string : (int, std::string)), и мы могли бы иметь еще 10. Все (или некоторые из них) принимают разные типы аргументов и могут возвращать разные типы. Мы хотим хранить их в std::map, поэтому мы получаем API, подобный этому:

//Having a functions like:
int hello_world(std::string name, const int & number )
{
    name += "!";
    std::cout << "Hello, " << name << std::endl;
    return number;
}
//and
void i_do_shadowed_stuff()
{
    return;
}

//We want to be capable to create a map (or some type with similar API) that would hold our functional objects. like so:
myMap.insert(std::pair<std::string, fun_object>("my_method_hello", hello_world) )
myMap.insert(std::pair<std::string, fun_object>("my_void_method", i_do_shadowed_stuff) )
//And we could call tham with params if needed:
int a = myMap["my_method_hello"]("Tim", 25);
myMap["my_void_method"];

Интересно, как поместить много разных функций в один контейнер? В частности, как это сделать в C ++ 03 с помощью Boost.

API должен быть независим от фактических типов функций (имеющих int a = myMap["my_method_hello"]("Tim", 25);, а не int a = myMap<int, (std::string, int)>["my_method_hello"]("Tim", 25);).

Ответы [ 4 ]

7 голосов
/ 29 ноября 2011
#include <functional>
#include <iostream>
#include <string>
#include <map>

class api {
    // maps containing the different function pointers
    typedef void(*voidfuncptr)();
    typedef int(*stringcrintptr)(std::string, const int&);

    std::map<std::string, voidfuncptr> voida;
    std::map<std::string, stringcrintptr> stringcrint;
public:
    // api temp class
    // given an api and a name, it converts to a function pointer
    // depending on parameters used
    class apitemp {
        const std::string n;
        const api* p;
    public:
        apitemp(const std::string& name, const api* parent)
            : n(name), p(parent) {}
        operator voidfuncptr()
        { return p->voida.find(n)->second; }
        operator stringcrintptr()
        { return p->stringcrint.find(n)->second; }
    };

    // insertion of new functions into appropriate maps
    void insert(const std::string& name, voidfuncptr ptr)
    { voida[name]=ptr; }
    void insert(const std::string& name, stringcrintptr ptr)
    { stringcrint[name]=ptr; }
    // operator[] for the name gets halfway to the right function
    apitemp operator[](std::string n) const
    { return apitemp(n, this); }
};

Использование:

api myMap; 

int hello_world(std::string name, const int & number )
{
    name += "!";
    std::cout << "Hello, " << name << std::endl;
    return number;
}

int main()
{
    myMap.insert("my_method_hello", &hello_world );
    int a = myMap["my_method_hello"]("Tim", 25);
}

http://ideone.com/SXAPu Не очень красиво. Лучший совет - не делать ничего даже отдаленно, как бы ты ни пытался.

Обратите внимание, что для этого требуется, чтобы все функции с одинаковыми параметрами возвращали одинаковый тип.

4 голосов
/ 29 ноября 2011

Вы можете использовать boost :: any ...

#include <boost/any.hpp>
#include <iostream>
#include <map>
#include <string>

void voidFunc()
{
    std::cout << "void called" << std::endl;
}

void stringFunc(std::string str)
{
    std::cout << str << std::endl;
}

int main()
{
    std::map<std::string, boost::any> funcs;
    funcs.insert(std::pair<std::string, boost::any>("voidFunc", &voidFunc));
    funcs.insert(std::pair<std::string, boost::any>("stringFunc", &stringFunc));

    boost::any_cast<void(*)(void)>(funcs["voidFunc"])();
    boost::any_cast<void(*)(std::string)>(funcs["stringFunc"])("hello");
    return 0;
}

Обратите внимание, что вы получите исключение во время выполнения, если вы не укажете подпись функции правильно в any_cast.

2 голосов
/ 29 ноября 2011

Дело в том, что когда вы вызываете свои функции, вы уже знаете, какого типа они будут.

Если мы сделаем что-то вроде

int x = map["key"](1, "2")

мы уже можем сделать вывод, что любая функция, хранящаяся в «ключе», имеет тип (int (*)(int, char*)), поэтому мы могли бы сделать что-то вроде

int x = map_of_int_and_string_to_int["key"](1, "2");

и избегайте всех хлопот, связанных со слиянием всех ключей ... Хотя это правда, что C ++ имеет некоторые функции перегрузки именно для такого рода вещей, я не могу понять, почему вы должны беспокоиться в этом конкретном случае.

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

0 голосов
/ 29 ноября 2011

Вы можете сделать это, приведя указатели функций к пустым указателям и обратно.От вас ожидают, что вы узнаете подпись во время выполнения, поэтому не будет проблем с жесткой связью операторов приведения.Однако логика этого ускользает от меня.Это вообще не имеет смысла, по крайней мере, в C ++.Использование шаблонных классов / функций или структур указателей на функции имеет гораздо больший смысл.

Например, с шаблонами:

template <typename X> foo(X param1) { /* do something with param1*/};
template <typename X, typename Y> foo(X param1, Y param2)
   {/* do something with 2 params*/};
template <int X> foo(X param1) { /* only one parameter, which is int */};

сейчас:

foo(5); // calls the third one
foo("5"); // calls the first one
foo("5", 5); // calls the second one.

Кому нуженкарта

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...