Question

(I see that similar questions have previously been asked on SO, but the ones I've seen don't appear to touch on my use-case exactly. In particular, I'm wanting to know if my compilation failure is the result of a mistake, or the result of my attempting what is verboten.)

Background

I wish to implement the delegate pattern, for event handling. I think perhaps the best approach for my needs is a map of member function pointers, indexed by std::string (which represent the event types.)

I started out trying to accomplish this with std::function, but ran into some problems, and then decided to try over with just raw MFPs. (I am still willing to consider std::function, and I will accept an answer that shows how to accomplish my exact needs below, using that approach. But I would still like to know what is wrong with my current approach.)

I was able to get this working with a single class. However I actually want the delegate map to be provided by an abstract base class, and then have a derived class register its delegates into that map. Unless I've made some mistake in the code below, this appears not to be possible; it appears that member function pointers cannot be polymorphic.

The compilation errors I get look like this:

mfp5.cpp: In constructor ‘Derived::Derived()’:
mfp5.cpp:41:21: error: cannot convert ‘int (Derived::*)(const Base::EventContext&)’ to ‘std::map<std::basic_string<char>, int (Base::*)(const Base::EventContext&)>::mapped_type {aka int (Base::*)(const Base::EventContext&)}’ in assignment
_delegates["foo"] = &Derived::FooEventHandler;

Questions

  1. Did I make a mistake in the code below, or is this truly disallowed? In a nutshell, I have a std::map of Base::*, and I want to insert some Derived::* into it.
  2. Is there some other recommended method to accomplish this?

Code

class Base
{
  public:
    struct EventContext
    {
      int data1;
    };
    Base() {}
    virtual int ProcessEvent(std::string event, EventContext ctx) =0;

  protected:
    typedef int (Base::* EventHandler)(const EventContext& context);
    typedef std::map<std::string, EventHandler> EventDelegateMap;
    EventDelegateMap _delegates;
};

.

class Derived: Base
{
  public:
    Derived();
    int ProcessEvent(std::string event, EventContext ctx);

  private:
    int FooEventHandler(const EventContext& context);
    int BarEventHandler(const EventContext& context);
    int QuxEventHandler(const EventContext& context);
};

Derived::Derived() :Base()
{
  _delegates["foo"] = &Derived::FooEventHandler;  // error
  _delegates["bar"] = &Derived::BarEventHandler;  // error
  _delegates["qux"] = &Derived::QuxEventHandler;  // error
}
Was it helpful?

Solution

It seems you want to use std::function, I would say something like:

class Base
{
  public:
    struct EventContext
    {
      int data1;
    };
    Base() {}
    virtual int ProcessEvent(std::string event, EventContext ctx) =0;

  protected:
    typedef std::function<int(const EventContext&)> HandlerType;
    typedef std::map<std::string, HandlerType> EventDelegateMap;
    EventDelegateMap _delegates;
};

class Derived: Base
{
  public:
    Derived();
    int ProcessEvent(std::string event, EventContext ctx){ return 0; }

  private:
    int FooEventHandler(const EventContext& context){ return 0; }
    int BarEventHandler(const EventContext& context){ return 0; }
    int QuxEventHandler(const EventContext& context){ return 0; }
};

Derived::Derived() :Base()
{
    auto self = this; // Some gcc versions cannot capture this correctly.
    _delegates["foo"] = [=](const EventContext& context) { return self->FooEventHandler(context); };
    _delegates["bar"] = [=](const EventContext& context) { return self->BarEventHandler(context); };
    _delegates["qux"] = [=](const EventContext& context) { return self->QuxEventHandler(context); };
}

Ought to work...

EDIT: As @Joachim mentions in his comment, you can use std::bind() to generate the required std::function object too, e.g.

_delegates["foo"] = std::bind(&Derived::FooEventHandler, this, std::placeholders::_1);

I used the lambda to show that in reality, you can implement the entire logic in the lambda. The primary advantage of this approach is that if you are implementing more handlers, it's less effort and I'm always in favour of less effort... :)

OTHER TIPS

I believe that this is a type error because if the assignment were allowed, you could do something like this:

/* This step is iffy... */
void (Base::* basePtr)() = &Derived::someMethod;

/* ... because of this. */
Base b;
(b.*basePtr)(); // Ooops...

Here, in the last line, we call the function pointed at by basePtr inside the Base object b. But that's a problem, since the receiver object is supposed to have type Derived!

Hope this helps!

Input arguments to polymorphic function, e.g., the this pointer passed to a member function pointer, can only be contravariant. Since this is also an output argument and output arguments can only be covariant, this has to be invariant but that part has nothing to do with your question. These statements derive immediately from the Liskov Substitution Principle.

Basically, you cannot do the assignment you are attempting to do because the compiler can't prove that your member function of a derived type is always called on a derived object.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top