1

I'm creating a class called MessageableObject, that can register its own member functions in a map. These functions can be called from outside by calling sendMessage, providing a return type as a template parameter, and passing the name of the function, as well as the paramters as variadic arguments. I've seen something very similar to this done before, only the functions were not member functions of the class storing them.

Below is the code I've written thus far, as well as a test case that should succeed.

#include <iostream>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>

class MessageableObject
{
    using _message_ptr = void(MessageableObject::*)(void);

    std::unordered_map<const char*, std::pair<_message_ptr, std::type_index>> _message_map;

public:

    MessageableObject()
    {
    }

    template<typename F>
    void addMessage(const char* name, F func)
    {
        auto type = std::type_index(typeid(func));
        _message_map.insert(std::make_pair(name, std::make_pair((_message_ptr)func, type)));
    }

    template<typename R, typename... Args>
    R sendMessage(const char* name, Args&&... args)
    {
        auto iter = _message_map.find(name);
        assert(iter != _message_map.end());
        auto type = iter->second;
        auto func = reinterpret_cast<R(MessageableObject::*)(Args ...)>(type.first);
        assert(type.second == std::type_index(typeid(func)));
        return func(std::forward<Args>(args)...);
    }

    void foo()
    {
        std::cout << __FUNCTION__ << std::endl;
    }

    int bar(int i, int j)
    {
        std::cout << __FUNCTION__ << ' ' << i + j << std::endl;
        return i + j;
    }
};

int main()
{
    MessageableObject obj;
    obj.addMessage("foo", &MessageableObject::foo);
    obj.addMessage("bar", &MessageableObject::bar);

    obj.sendMessage<void>("foo");
    int baz = obj.sendMessage<int>("bar", 1, 2);

    return 0;
}

Currently, this code generates the following error twice:

C2064: term does not evaluate to a function taking n arguments

The first time with n being 0 for when I attempt to call foo, and n being 2 for when I attempt to call bar. Why are these errors occuring? And can I even implement this using the current syntax?

For reference, the version of this implementation that works on non-member functions can be found here: https://stackoverflow.com/a/33837343/962805

Any and all help is greatly appreciated. I'm not very knowledgeable on type erasure and member function binding. The intent for this is to create dynamic messaging a-la unity engine's GameObject.sendMessage function.

Update: As according to skypjack's answer below, the above solution will break if the message call contains a variable. It can be fixed by changing Args&&... in sendMessage to Args..., however this poses a new issue: if a message is added to the map that contains a reference parameter (such as int bar(int&, int)), it will not be able to be called implicitly. Calling sendMessage<int, int&, int>("bar", x, 2) will function as intended.

Community
  • 1
  • 1
dyeo
  • 15
  • 4

1 Answers1

2

If you include <cassert> at the top of the file and use this:

return (this->*func)(std::forward<Args>(args)...);

Instead of this:

return func(std::forward<Args>(args)...);

It compiles.

I cannot say that it works for it looks to me you are fortunately avoiding an UB and it apparently works (that is a perfectly valid UB) would be more appropriate.
The problem is that bar has type:

int(MessageableObject::*)(int i, int j);

And you cast it cast to:

void(MessageableObject::*)(void);

Then you cast back it to it's correct type for you are invoking it as:

obj.sendMessage<int>("bar", 1, 2);

If you did instead this:

int x = 1;
obj.sendMessage<int>("bar", x, 2);

You would have performed a cast to:

int (MessageableObject::*)(int &i, int j);

Something similar would happen with a wrong return type. All these are UB. Therefore too weak a solution to use it in a production environment from my point of view.

skypjack
  • 49,335
  • 19
  • 95
  • 187
  • Thank you! This solution compiles and does indeed work. Now I just need to find a more robust solution for handling variables as arguments and avoiding UB. – dyeo Mar 15 '17 at 15:32