简体   繁体   中英

Can I automatically deduce child type in base class template method?

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, ie 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");

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

NB 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); });
  }
  // ...
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM