4

Context

(C++11) As part of a safeguard in a piece of serialization code, I want to check if a function pointer is known. (Otherwise, the deserialization mechanism will probably fail).

This is a (simplified) piece of code that illustrates the mechanism:

template <typename Fun>
struct FunctionCallRegistry
{
    static std::unordered_map<Fun, std::string>& FunctionMap()
    {
        static std::unordered_map<Fun, std::string> map;
        return map;
    }

    static void Register(Fun function, std::string name)
    {
        auto& map = FunctionMap();
        auto it = map.find(function);
        if (it == map.end()) 
        {
            map.insert(std::pair<Fun, std::string>(function, name));
            // other code follows...
        }
    }

    static void Ensure(Fun function) 
    {
        auto& map = FunctionMap();
        auto it = map.find(function);
        if (it == map.end())
        {
            throw std::exception("Function not found.");
        }
    }
};

// Registration code:
struct FunctionCallRegistryInitializer
{
    template <typename Ret, typename... Args>
    explicit FunctionCallRegistryInitializer(Ret(*function)(Args...),
                                             const char* name)
    {
        FunctionCallRegistry<Ret(*)(Args...)>::Register(function, name);
    }
};

#define RegisterFunction(fcn) FunctionCallRegistryInitializer fcn_reg_##__COUNTER__(&fcn, #fcn);

Serialization is now easy, given that we can lookup a function and emit the name. Similarly, deserialization looks up the name and emits the function factory wrapped in a class (this is the easy part).

Registration works as follows: basically for each function, I'll call Register with a function pointer and a name (which is used for the (de)serialization). There's a macro involved here and a few helper classes to do the plumbing. E.g.:

struct Testje
{
    static int test(int lhs, int rhs) {
        std::cout << lhs << " + " << rhs << std::endl;
        return lhs + rhs;
    }
};

RegisterFunction(Testje::test);

The type of Fun here is f.ex. void (*fcn)(int, int). This works fine, because test is a static function and hence not a member function. Remove the 'static' and the horror begins...

Problem

When I attempt to compile this code in MSVC++, I get an error that tells me function-to-member pointers cannot be hashed:

error C2338: The C++ Standard doesn't provide a hash for this type. 

In an attempt to solve this, I've tried to cast fcn to another pointer type, which doesn't seem to be allowed as well.

Note that member function pointers aren't required to be unique; I just need a unique ID for each member function pointer with the same signature since Fun is a template. It's also easy to ensure that the name is unique by simply throwing an exception if it's registered twice.

Question

This leaves me with the simple question: what will work? How can I check if a function pointer is registered?

atlaste
  • 30,418
  • 3
  • 57
  • 87
  • I don't want to suggest this as an answer, as my C++ knowledge is not so good, but Error C2338 may actually be saying "This type can't be hashed because it's incomplete - it's just a template parameter, and as I am compiling the template, I don't know exactly what sort of type it is". Maybe you need to pass Fun* to the template, rather than Fun? – Jack Whitham Apr 22 '15 at 13:11
  • *"I've tried to cast fcn to another pointer type"* You can convert *the address of the function pointer object* to another type. Or you can `memcpy` the function pointer object to an array of characters. – dyp Apr 22 '15 at 13:11
  • 4
    Caution : `FunctionMap()` returns by value, it should return by reference. – Quentin Apr 22 '15 at 13:13
  • To clarify: the problem is only with _pointers to member functions_, not simply with _pointers to functions_. In the latter case it all works just fine. I've been attempting to use this fact as a potential work-around, so far without success. – atlaste Apr 22 '15 at 13:13
  • 2
    @Quentin Similarly the return value should be stored in a reference variable: `auto map = FunctionMap();` --> `auto& map = FunctionMap();` – dyp Apr 22 '15 at 13:14
  • 3
    It looks really strange that you use `std::unordered_map` and not `std::unordered_map`. Can you update your post with an example of intended usage of `FunctionCallRegistry`? – Anton Savin Apr 22 '15 at 13:15
  • Thanks for your clarification. So the trouble is that it's not just one pointer, it's both a pointer to the object and a pointer to a member function within it. Ouch. This level of C++ is beyond me. I suppose you have seen https://isocpp.org/wiki/faq/pointers-to-members#memfnptr-vs-fnptr ? – Jack Whitham Apr 22 '15 at 13:25
  • @JackWhitham yes, I know about that. However, these points only hold if the signature varies - in my case I only need an ID given a unique signature (Fun), in which case the vtable indexes etc. won't pose a problem. Since I only need a unique ID, that also gives opportunity for a work-around, iff I can explain C++ that I can use a non-member function as a wrapper... so far I've been unsuccesful in my efforts though. – atlaste Apr 22 '15 at 13:32
  • @atlaste, can you explain how do you going to use `FunctionCallRegistry` with member function pointers? You can't get function pointer without object. All you need just check whether member function registered, isn't it? – gomons Apr 22 '15 at 13:51
  • @gomons Basically (1) I want to check if it exists and (2) I want to lookup the name (the std::string) for a member function. – atlaste Apr 22 '15 at 13:53
  • Can you get away with a `std::map` container? I don't know if pointer-to-members are less-than comparable but if they are this circumvents the missing `std::hash` specialization. Alternatively you could provide [your own hash implementation](http://stackoverflow.com/questions/1328238/how-to-hash-and-compare-a-pointer-to-member-function). – Roland W Apr 22 '15 at 14:32
  • Would wrapping the member functions in non member function be acceptable ? – Serge Ballesta Apr 22 '15 at 14:37
  • @SergeBallesta Yes. I've been fiddling with that; just haven't had any luck with it. Instead of `Call(&memfcn)` a `Call<&memfcn>()` would also be just fine (the #define can be easily adjusted). However, I haven't been able to get that to work as well. – atlaste Apr 22 '15 at 14:47
  • do you consider wrapping stuff in a `std::function<>` to expensive? – Alexander Oh Apr 24 '15 at 07:18
  • @Alex, no. I've made an attempt to use `std::bind` etc. before going down this road, but it didn't work out. For me the end result of that was having to type too much code in the long run, so I abandoned that approach... In general there's nothing wrong with `std::function`; it just doesn't appear to work with what I have in mind. – atlaste Apr 24 '15 at 07:26

4 Answers4

1

I modify your code, now it works only for one member-function. It is just an example:

// Registration code:
struct FunctionCallRegistryInitializer
{

    // Here I changed Ret(*function) to Ret(T::*function)
    template <typename T, typename Ret, typename... Args>
    explicit FunctionCallRegistryInitializer(Ret(T::*function)(Args...),
                                             const char* name)
    {
        FunctionCallRegistry<Ret(T::*)(Args...)>::Register(function, name);
    }
};

// We need it for use use member-function test like key value in map
namespace std {
    template <> 
    struct hash<decltype(&Testje::test)>
    {
        size_t operator()(decltype(&Testje::test) x) const
        {
            return typeid(x).hash_code();
        }
    };
}

RegisterFunction(Testje::test);

Than we can check whether member-function registered:

FunctionCallRegistry<decltype(&Testje::test)>::Ensure(&Testje::test);

It is terrible solution, I know, but may be it can be improved.

gomons
  • 1,946
  • 14
  • 25
  • decltype(function) seems to give `int (Testje::*)(int, int)` here, of which there could be plenty... this was one of the issues I was having. Still, it was an interesting idea. – atlaste Apr 22 '15 at 14:54
  • I think this is in a way similar to the end-solution I posted... just change the decltype stuff into a simple `template ` and you're golden. – atlaste Apr 23 '15 at 09:02
  • @atlaste, do you mean `hash()` function? – gomons Apr 23 '15 at 11:28
  • Yes. Basically: `struct hash { template size_t operator()(const T& x) const { return typeid(T).hash_code(); }`. Or, in my case I could just return '1' or use a simple vector, as in the last answer I posted. – atlaste Apr 23 '15 at 11:45
1

I could not find a way to take the address of a member function in a portable way. But I can propose 2 workarounds.

Wrapping the member function into a simple function

We all know that a member function is equivalent to a function taking its object as first argument. So if we have :

struct Testje
{
    int test(int lhs, int rhs) {
        std::cout << lhs << " + " << rhs << std::endl;
        return lhs + rhs;
    }
};

it is easy to wrap it with

template<class T, int(T::*mf)(int, int)>
int wrapper(T& obj, int lhs, int rhs) {
        return (obj.*mf)(lhs, rhs);
}
...
FunctionCallRegistry<int (*)(Testje&, int, int)> reg;
reg.Register(&(wrapper<Testje, &Testje::test>), "test");

This compiles and runs fine with a C++11 compiler (provided you apply the fixes suggested by Quentin and dyp - see my below implementation)

Implement a hash class for member functions

You cannot use a pointer to member function as a key in an unordered_map because it cannot be directly hashed. But you can implement a hash for it as suggested by Roland W :

class Testje{
public:
    int test(int lhs, int rhs) {
        std::cout << lhs << " + " << rhs << std::endl;
        return lhs + rhs;
    }
    static int myhash(int (Testje::*f)(int, int) f) {
        // for each and every member function with that signature return a different int
        if (f == &Testje::test) {
             return 0;
        }
        return -1;
    }
    class Hash {
    public:
        size_t operator( )(int (Testje::*f)(int, int) f) const {
            return myhash(f);
        }
    };
};

You have then just to change FunctionCallRegistry to use the new hash function :

template <typename Fun, class Hash = std::hash<Fun>>
struct FunctionCallRegistry
{
    // not static to allow multiple registries
    std::unordered_map<Fun, std::string, Hash> _map; 

    std::unordered_map<Fun, std::string, Hash>& FunctionMap() // returns a reference ...
    {
        return _map;
    }

    void Register(Fun function, std::string name)
    {
        std::unordered_map<Fun, std::string, Hash>& map = FunctionMap();
        auto it = map.find(function);
        if (it == map.end())
        {
            map.insert(std::pair<Fun, std::string>(function, name));
            // other code follows...
        }
    }

    void Ensure(Fun function)
    {
        std::unordered_map<Fun, std::string, Hash>& map = FunctionMap();
        auto it = map.find(function);
        if (it == map.end())
        {
            throw std::exception();
        }
    }
};

You can then use it with :

FunctionCallRegistry<int (Testje::*)(int, int), Testje::Hash> reg;

reg.Register(&Testje::test, "test");
reg.Ensure(&Testje::test);

This is a little more complex but you directly register the member functions.

Note : both method transparently accept virtual member functions.

Community
  • 1
  • 1
Serge Ballesta
  • 143,923
  • 11
  • 122
  • 252
  • I think the wrapper is the only good solution here... I have literally thousands of functions. My struggle with that was to write it as a template. Do you happen to know if it's possible to write that with a (variadic) template so that it will accept any method signature? – atlaste Apr 23 '15 at 06:46
  • @atlaste I must admit I'm already bewond my current knowledge level about templates and other C++11 features. I think you should ask a new question for that with reference to present one. – Serge Ballesta Apr 23 '15 at 06:56
  • I think the problems are intertwined too much for that. I've pursued this line of thought as well, but didn't get it to compile. – atlaste Apr 23 '15 at 09:04
1

Even though I post this answer, most credits should go to Mikael Kilpeläinen. And even though I don't make it a habit to accept my own answers, I feel this is not my answer anyways and the only one here that absolutely solves it. Thanks Mikael!

As it turns out there was actually a very simple solution. The main observation I should have made is that there aren't that many functions with the exact same signature (which of course includes the class type and arguments) for each type.

The second important observation is that equality comparison is possible for member function pointers.

The result is surprisingly simple: put the functions in a vector or let the hash code simply return a constant (such as 1). To summarize:

template <typename Fun>
struct FunctionCallFactory
{
private:
    static std::vector<std::pair<Fun, std::string>>& FunctionMap()
    {
        static std::vector<std::pair<Fun, std::string>> map;
        return map;
    }

public:
    static void Register(Fun function, std::string name)
    {
        auto& map = FunctionMap();
        for (auto& it : FunctionMap())
        {
            if (it.first == function)
            {
                throw std::exception("Duplicate registration of function. Not allowed.");
            }
        }
        map.push_back(std::pair<Fun, std::string>(function, name));
    }

    // etc.
};

Note that for each Fun a separate instance will be made, which avoids most collisions during the search. As such, a linear scan will work.

If you want to use a hasher and don't want to put Fun in a template, use the typeid of the function as hash value. Implementation of that is quite easy as well.

atlaste
  • 30,418
  • 3
  • 57
  • 87
0

OK, I've found the worst-case-scenario answer here. Get memory address of member function?

Basically this involves casting the member-function pointer to (void*&).

Let's call this 'plan Z'... it's obviously not portable nor maintainable. I'm just posting it here as 'answer' because it will probably work. Any other answer is imho highly preferable.

Community
  • 1
  • 1
atlaste
  • 30,418
  • 3
  • 57
  • 87