0

For a start, let me provide a bit context. I'm creating a small game framework for my game, where I have gameplay system and event system. I try to use language features such as templates to avoid boilerplate in user code.

There is Component class, from which other game elements are derived, i.e. Camera, Sprite, Button.

using EventType = unsigned int;
using EventCallback = std::function<void(Event)>;

class Component {
public:
    // This is public version for general functor objects,
    // particularly used with lambda
    void addHandler(EventType type, const EventCallback &callback);

protected:
    // Method for child classes. Creates event handler from method of the class.
    //
    // This is template method, because std::bind is used for functor creation,
    // which requires class type at compile time.
    template <class T>
    void addHandler(EventType type, void (T::*method)(Event event)) { /*...*/ }
private:
    // ...
};

Every component listens to particular set of events, so it doesn't have to implement handlers for every possible type of event. Also, component user should be able to add custom event listeners without creating new class for each game element, for example:

class Button : public Component {
public:
    Button() {
        addHandler(kOnTouch, &Button::onTouch);
    }
    // ...
};

Button ok, cancel;
ok.addHandler(kOnClick, [](Event) {
    // ...
});

cancel.addHandler(kOnClick, [](Event) {
    // ...
});

// Add another handler somewhere else in the code
cancel.addHandler(kOnClick, someCallback);

So, what I want to do is late binding, with compile-time check for member functions. I want to ensure that method pointer passed to addHandler() belongs to child class that called addHandler(). I can get type of method owner inside addHandler() with help of template argument deduction. But I didn't find a way to deduce child class type. Here how I tried to accomplish this with decltype(*this) and type traits:

template <class T>
void addHandler(EventType type, void (T::*method)(Event event)) {
    /***** This check is INCORRECT *****/
    // Check that T is same as child class
    using ChildClass = std::remove_reference<decltype(*this)>::type;
    static_assert(std::is_same<T, ChildClass>::value,
                  "Event handler method must belong to caller class");

    using namespace std::placeholders;

    EventHandler::Callback callback =
            std::bind(method, static_cast<T *>(this), _1);
    addHandler(EventHandler(type, callback));
}

Here we need child class type to compare T with. It seems that ChildClass being assigned to base Component class rather than to child. Is there a way to deduce child class type automatically, changing only inside of method-version addHandler()? It's important to template only this overloaded addHandler() and not the whole Component class to minimize generated code and be able to use polymorphism. So it is tiny wrapper around more generic addHandler(), taking std::function.

At the moment, I can only check that T is Component:

static_assert(std::is_base_of<Component, T>::value,
              "Event handler method must belong to caller class");
Ivan Perevezentsev
  • 66
  • 1
  • 2
  • 11
  • Well, you need to decide whether you want to use early or late binding. If you want to use early binding, do not use a common sub-class. – Šimon Tóth Nov 30 '15 at 15:29
  • @Let_Me_Be I want late (runtime) binding as opposed to early (compile time), so user code can manage event handlers i.e. in conditional statements. http://stackoverflow.com/questions/10580/what-is-the-difference-between-early-and-late-binding – Ivan Perevezentsev Nov 30 '15 at 17:03

1 Answers1

1

It seems that ChildClass being assigned to base Component class rather than to child.

Nothing is being "assigned" here. It's confusing to misuse terminology.

The type of this in a member function of the base class is (of course) the base class.

To know the type of the caller you need the this pointer of the caller, not the this pointer inside the base class member function. You can get that this pointer of the caller by requiring the caller to pass it as an argument:

template <class T, class U>
  void addHandler(EventType type, void (T::*method)(Event event), U* that)

Then you don't even really need the static assertion, you can just bind that to method

N.B. you might want to use std::is_base_of not std::is_same if you do keep the static assertion.

Alternatively you could get rid of the addHandler overload and just require the derived types to do the binding:

class Button : public Component {
public:
  Button() {
    addHandler(kOnTouch, [this](Event e) { onTouch(e); });
  }
  // ...
};
Jonathan Wakely
  • 166,810
  • 27
  • 341
  • 521
  • Of course this is not assignment like with variables, I just used word that makes sense. Interesting option with `this` transfer. But it would be nice to avoid it. The point of overload actually is to not to use lambdas. – Ivan Perevezentsev Nov 30 '15 at 16:10
  • If I would use `std::is_base_of` then user might accidently pass pointer to other component's member and this can cause crash because of binding to object instance of other type (notice `static_cast`). The `static_cast` is needed by std::bind to resolve overload. – Ivan Perevezentsev Nov 30 '15 at 16:19
  • 1
    No, not if you use `is_base_of` correctly. It's fine to do: `struct FancyButton : Button { FancyButton() { addHandler(kOnTouch, &Button::onTouch); } };` because `FancyButton` is a `Button` and so the `static_cast` will be correct. You do not require the types to be the same, you only require the cast to be correct, and it's correct if the pointer you bind to the method is the type of the method _or a type publicly and unambiguously derived from it_ ... and that's what `is_base_of` tells you. – Jonathan Wakely Nov 30 '15 at 16:35