我可以在基类模板方法中自动推导子类型吗

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

本文关键字:类型 基类 模板方法 我可以      更新时间:2023-10-16

首先,让我提供一个位上下文。我正在为我的游戏创建一个小游戏框架,其中我有游戏系统和事件系统。我尝试使用诸如模板之类的语言特性来避免用户代码中的样板文件。

有一个Component类,从中派生出其他游戏元素,即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:
    // ...
};

每个组件都侦听特定的事件集,因此不必为每种可能的事件类型实现处理程序。此外,组件用户应该能够添加自定义事件侦听器,而无需为每个游戏元素创建新的类,例如:

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);

所以,我想做的是后期绑定,对成员函数进行编译时检查。我想确保传递给addHandler()的方法指针属于调用addHandler的子类。在模板参数推导的帮助下,我可以在addHandler()中获取方法所有者的类型。但我没有找到一种方法来推断孩子的班级类型。以下是我如何尝试使用decltype(*this)和类型特征来实现这一点:

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));
}

这里我们需要子类类型来比较T。似乎ChildClass被分配给了基类Component,而不是子类。有没有一种方法可以自动推导子类类型,只在方法版本addHandler()内部更改?重要的是,只为这个重载的addHandler()模板,而不是整个Component类模板,以最大限度地减少生成的代码并能够使用多态性。因此,它是一个更通用的addHandler()的小型包装器,采用std::函数。

目前,我只能检查T是否为组件:

static_assert(std::is_base_of<Component, T>::value,
              "Event handler method must belong to caller class");

似乎ChildClass被分配给了基类Component,而不是子类。

此处未"分配"任何内容。滥用术语令人困惑。

基类的成员函数中this的类型(当然)是基类。

要知道调用者的类型,需要调用者的this指针,而不是基类成员函数内的this指针。您可以通过要求调用者将其作为参数传递来获得调用者的this指针:

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

那么您甚至不需要静态断言,只需将that绑定到method 即可

注意:如果保留静态断言,则可能希望使用std::is_base_of而不是std::is_same

或者,您可以去掉addHandler重载,只需要派生类型来进行绑定:

class Button : public Component {
public:
  Button() {
    addHandler(kOnTouch, [this](Event e) { onTouch(e); });
  }
  // ...
};