1

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?

Community
  • 1
  • 1
Matthias
  • 9,817
  • 14
  • 66
  • 125

2 Answers2

4

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.

StoryTeller - Unslander Monica
  • 165,132
  • 21
  • 377
  • 458
  • So do I understand you correctly that even with those placeholders it wouldn't work? If so, is there a valid solution then to my requirement? – Matthias Dec 12 '16 at 14:45
  • @Story you might want to include a note that this is not very safe because you might call "Greater" with any number of arguments. – Christoph Diegelmann Dec 12 '16 at 15:00
  • 2
    @Christoph Thanks. I'm cooking up the `std::type_info` solution at the moment. Stay tuned – StoryTeller - Unslander Monica Dec 12 '16 at 15:07
  • Thank you so much, your code works really great. Exactly what I needed. Unfortunately I don't understand all of it, so I need to do some research... I don't want to bother you with explaining every little detail, but would you mind linking me to the correct C++ docs for the `using cbType = bool(*)();` line, as well as for `using prototype = bool(*)(argsType...);`? In any case, thank you very much again! – Matthias Dec 12 '16 at 15:43
  • 1
    @Matthias I will edit the post with links to relevant language features. Will be some time later in the next 24 hours – StoryTeller - Unslander Monica Dec 12 '16 at 15:45
  • One more question: Is there any way to use your solution with overloaded functions, i.e. when only the parameter type changes? For example, `static bool Greater(const int A, const int B)` and `static bool Greater(const float A, const float B)`. With the current solution I get the error *"Cannot convert overloaded functions to parameter type 'bool(*)()'"*. – Matthias Dec 12 '16 at 22:49
  • 1
    @Matthias - Please refer to my edit for what is (I hope) the info you requested. Storing overloaded functions is also possible. But each overload is a separate function, that will require it's own key. To disambiguate the call to `make_map_val`, you can pass the type of the first argument to the function pointer, say like this `make_map_val`. It's probably possible to rewrite the mechanism in a way that will store all overloads under the same key, and will call the correct one if it exists, but I think my answer is already quite long :) – StoryTeller - Unslander Monica Dec 13 '16 at 09:28
1

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

user5821508
  • 332
  • 2
  • 10