如何使 std::function<void(X)> 接受第一个参数是 X 的多态子类型的 lambda?

问题描述 投票:0回答:1

假设我有一个像这样的通用

Event
类:

// Event (Generic)
struct Event {
    // Event type
    std::string m_type;

    // c-tor
    Event(const std::string& type) : m_type(type) { /* empty */ }

    // d-tor (polymorphic)
    virtual ~Event() = default;
};

假设我有一个

KeyEvent
类,它的子类
Event
:

// Event (Keyboard)
struct KeyEvent : Event {
    // Key 
    char m_key;

    // Key down or up
    bool m_down;

    // c-tor
    KeyEvent(char key, bool down) 
        : Event(down ? "keydown" : "keyup")
        , m_key(key)
        , m_down(down) { /* empty */ }
};

假设我有一个通用的

Component
类,它可以监听并触发上面定义的事件

// Component (listens to events)
class Component {

    // ...

    std::map<std::string, std::vector<std::function<void(const Event&)>>> m_events;

    // ...

public:

    // Listen to event
    void listen(const std::string& type, std::function<void(const Event&)> callback) {
        m_events[type].push_back(callback);
    }

    // Trigger event
    void trigger(const Event& event) {
        auto it = m_events.find(event.m_type);
        if (it != m_events.end()) {
            for (auto& callback : m_events[event.m_type]) {
                callback(event);
            }
        }
    }
};

我希望能够通过以下方式添加按键侦听器。

int main() {
    Component c;

    // Listen to key down event
    c.listen("keydown", [](const KeyEvent& keyEvent){
        std::cout << "You pressed '" << keyEvent.m_key << "'!\n";
    });

    // Trigger event
    c.trigger(KeyEvent('A', true));
}

但是这会导致以下编译器错误

main.cpp: In function ‘int main()’:
main.cpp:65:13: error: cannot convert ‘main()::<lambda(const KeyEvent&)>’ to ‘std::function<void(const Event&)>’
   65 |     c.listen("keydown", [](const KeyEvent& event) {
      |     ~~~~~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
   66 | 
      |              
   67 |     });
      |     ~~       
main.cpp:46:76: note:   initializing argument 2 of ‘void Component::listen(const string&, std::function<void(const Event&)>)’
   46 |     void listen(const std::string& type, std::function<void(const Event&)> callback) {
      |                                          ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~^~~~~~~~

我可以通过使用

[](const Event&){ ... }
(而不是
[](const KeyEvent&){ ... }
)轻松解决问题,然后明确地将
dynamic_cast
转换为
Event
KeyEvent

但是,我想知道是否有一种方法可以将 
int main() { Component c; // Listen to event c.listen("keydown", [](const Event& event){ auto& keyEvent = dynamic_cast<const KeyEvent&>(event); std::cout << "You pressed '" << keyEvent.m_key << "'!\n"; }); // Trigger event c.trigger(KeyEvent('A', true)); }

单独保留在

[](const KeyEvent&){ ... }
函数中,并修改
main()
的第二个参数,以便它毕竟允许这样的 lambda 函数?也许,我可以将回调参数包装在包装函数
void Component::listen(const std::string&, std::function<void(const Event&)>)
中,它可以在将其添加到
[](const Event&){ ... }
之前进行向下转换?
    

c++ c++17
1个回答
0
投票

m_events

这样您就可以进行这样的通话。

//template <std::derived_from<Event> T> only C++20 template <typename T> void listen(std::string const& type, std::function<void(T)> callback) { auto wrapper = [callback = std::move(callback)](Event const& ev) { callback(static_cast<T const&>(ev)); }; m_events[type].push_back(std::move(wrapper)); }

请注意,使用 C++20 概念 
c.listen<KeyEvent>("keydown", [](const KeyEvent& keyEvent){ // ^^^^^^^^ std::cout << "You pressed '" << keyEvent.m_key << "'!\n"; });

,您始终可以

std::derived_from
,而使用 C++17,如您的 OP 中标记的那样,您可能仍想使用
static_cast
并弄清楚如果有人打电话该怎么办您的
dynamic_cast
函数没有派生自
listen
的类型。
    

© www.soinside.com 2019 - 2024. All rights reserved.