How to create map of pointers to static member functions in C++11?

不羁岁月 提交于 2019-12-11 08:36:59

问题


I want to create a class that contains some static member functions and a map with pointers to these functions. They can, however, take different numbers and types of arguments. So following this thread I tried something like:

class BeliefCondFunc
{
    static std::unordered_map<std::string, std::function<bool()>> FuncMap;

    static bool Greater(int A, int B)
    {
        return A > B;
    }

    static bool Between(float A, float B, float C)
    {
        return A > B && A < C;
    }

    static void InitMap()
    {
        FunctionMap["Greater"] = std::bind(&BeliefCondFunc::Greater, ???);
        FunctionMap["Between"] = std::bind(&BeliefCondFunc::Between, ???);
    }
};

Now, what do I need to replace the ??? with in the code above to make it work? In the end I want to be able to call it like this:

BeliefCondFunc::FuncMap["Greater"](1, 2);
BeliefCondFunc::FuncMap["Between"](1.0f, 2.0f, 3.0f);

Is this even possible in C++11, without the use of an external library like boost?


回答1:


You need to replace the ??? with placeholders, to forward the arguments of the closure to the function.

std::bind(&BeliefCondFunc::Greater, _1, _2);

You should note however, that your map holds function objects that accept no arguments. So you'll likely get a call mismatch error. Since C++14, probably even on the attempted assignment into the newly constructed value.

I also feel you over complicate the solution. For static member functions, a simple function pointer type can suffice. You'd then need to preform some casting, however that can be encapsulated in another templated static member function. Now, casting function pointer types isn't very safe (think about what happens when you get the number arguments wrong). But you seem intent on sidestepping the type system already, so here it is:

#include <unordered_map>
#include <string>
#include <utility>

struct BeliefCondFunc
{
    using cb_type = bool(*)();
    static std::unordered_map<std::string, cb_type> const FuncMap;

    static bool Greater(int A, int B)
    {
        return A > B;
    }

    static bool Between(float A, float B, float C)
    {
        return A > B && A < C;
    }

    template<typename ...Args>
    static bool call(std::string const& key, Args&&... args){
      using prototype = bool(*)(Args...);
      return reinterpret_cast<prototype>(FuncMap.at(key))(std::forward<Args>(args)...);
    };
};

std::unordered_map<std::string, BeliefCondFunc::cb_type> const BeliefCondFunc::FuncMap {
  {"Greater", reinterpret_cast<cb_type>(&BeliefCondFunc::Greater) },
  {"Between", reinterpret_cast<cb_type>(&BeliefCondFunc::Between) }
};

int main() {
    BeliefCondFunc::call("Greater", 1, 2);
    return 0;
}

See it live


If the casting worries you, you can store the function pointers prototype's std::type_info pointer alongside with it. Then it's a simple matter of comparing the two and throwing an exception upon a mismatch at run time. Like so:

#include <unordered_map>
#include <string>
#include <utility>
#include <typeinfo>
#include <stdexcept>
#include <iostream>

struct BeliefCondFunc
{
    using cb_type = bool(*)();
    using map_val = std::pair<cb_type, std::type_info const*>;
    static std::unordered_map<std::string, map_val> const FuncMap;

    static bool Greater(int A, int B)
    {
        return A > B;
    }

    static bool Between(float A, float B, float C)
    {
        return A > B && A < C;
    }

    template<typename ...Args>
    static map_val make_map_val(bool (*func)(Args...)) {

        return std::make_pair(reinterpret_cast<cb_type>(func), 
                              &typeid(decltype(func)));
    }

    template<typename ...Args>
    static bool call(std::string const& key, Args&&... args){
      using prototype = bool(*)(Args...);

      auto& func_n_typeid = FuncMap.at(key);

      if (typeid(prototype) != *func_n_typeid.second )
        throw std::domain_error("Prototype mismatch");

      return reinterpret_cast<prototype>(func_n_typeid.first)(std::forward<Args>(args)...);
    };
};

std::unordered_map<std::string, BeliefCondFunc::map_val> const BeliefCondFunc::FuncMap {
  {"Greater", make_map_val(&BeliefCondFunc::Greater) },
  {"Between", make_map_val(&BeliefCondFunc::Between) }
};

int main() {
    BeliefCondFunc::call("Greater", 1, 2);

    try {
        BeliefCondFunc::call("Lesser", 1, 2);
    } catch (std::out_of_range&) {
        std::cout << "No such function\n";
    }

    try {
        BeliefCondFunc::call("Between", 1, 2);
    } catch (std::domain_error&) {
        std::cout << "Wrong number of arguments\n";
    }

    return 0;
}

Once again, see it live.

And as promised, an explanation:

  1. The following two lines create two type aliases (c++11 syntax, equivalent to a typedef, but clearly separates the new identifier from the type). I name the dummy callback type I'll store all pointers as, and the pair of values I'll store in the map.

    using cb_type = bool(*)();
    using map_val = std::pair<cb_type, std::type_info const*>;
    
  2. make_map_val is a template function that can be passed any function pointer which returns a bool. Whenever I call it with a function pointer, it uses template argument deduction to figure out the argument types of the function. I don't the argument types directly, but instead rely on decltype to get the type of the function. In retrospect, I could have just passed the function pointer directly to typeid, since it would have deduced the type the same way. The importance of using a tempalted function here, is to capture as much static type data about the function pointer automatically, without asking the call site of make_map_val for it.

  3. call again uses template argument type deduction to figure out the types of parameters for the function we want to call. A type alias is also used to conveniently reference the function pointer type we will ultimately cast to, as well as the parameter for typeid. The alias and function call itself are written using parameter pack expansion. The parameters are passed to the function with a forwarding reference, and onward to the function pointer via a call to std::forward, all to preserve the value category of the parameter. If all your functions operate only on small, cheap to copy values, you could probably get away with passing everything by value.




回答2:


if you want use different object types in one container there is 2 ways: 1. inheritance + RTTI 2. variant



来源:https://stackoverflow.com/questions/41102974/how-to-create-map-of-pointers-to-static-member-functions-in-c11

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!