Make Connect() work with overloaded event handlers in C++17

This used to work previously but got broken when using C++17 by
c3810da549 (Allow using noexcept methods with event tables macros,
2020-04-09), as cast added to deal with noexcept handlers relied on
argument type deduction which can't be done for overloaded functions.

Fix this by extracting the event argument type from the function pointer
type and specifying it explicitly, while still letting the compiler
deduce the class.

Add a test case checking that using overloaded event handlers compiles.

See #18721.

Closes #18896.
This commit is contained in:
Vadim Zeitlin
2020-11-16 22:41:47 +01:00
parent 1afd2248d7
commit f48099e00a
2 changed files with 47 additions and 21 deletions

View File

@@ -144,43 +144,58 @@ inline wxEventFunction wxEventFunctionCast(void (wxEvtHandler::*func)(T&))
wxGCC_WARNING_RESTORE_CAST_FUNCTION_TYPE()
}
// Special hack to remove "noexcept" from the function type when using C++17 or
// later: static_cast<> below would fail to cast a member function pointer to a
// member of a derived class to the base class if noexcept is specified for the
// former, so remove it first if necessary.
// In good old pre-C++17 times we could just static_cast the event handler,
// defined in some class deriving from wxEvtHandler, to the "functype" which is
// a type of wxEvtHandler method. But with C++17 this doesn't work when the
// handler is a noexcept function, so we need to cast it to a noexcept function
// pointer first.
#if __cplusplus >= 201703L
namespace wxPrivate
{
// Generic template version, doing nothing.
template <typename T>
struct RemoveNoexceptEventHandler
// Cast to noexcept function type first if necessary.
template <typename E, typename C>
constexpr auto DoCast(void (C::*pmf)(E&))
{
using type = T;
return static_cast<void (wxEvtHandler::*)(E&)>(pmf);
}
template <typename E, typename C>
constexpr auto DoCast(void (C::*pmf)(E&) noexcept)
{
return static_cast<void (wxEvtHandler::*)(E&)>(
static_cast<void (wxEvtHandler::*)(E&) noexcept>(pmf)
);
}
// Helper used to recover the type of the handler argument from the function
// type. This is required in order to explicitly pass this type to DoCast<> as
// the compiler would be unable to deduce it for overloaded functions.
// Generic template version, doing nothing.
template <typename F>
struct EventArgOf;
// Specialization sufficient to cover all event handler function types.
template <typename C, typename E>
struct EventArgOf<void (C::*)(E&)>
{
using type = E;
};
// Specialization removing noexcept when it's specified.
//
// Note that this doesn't pretend to be generally suitable, this is just enough
// to work in our particular case.
template <typename R, typename C, typename E>
struct RemoveNoexceptEventHandler<R (C::*)(E&) noexcept>
{
using type = R (C::*)(E&);
};
} // namespace wxPrivate
#define wxREMOVE_NOEXCEPT_EVENT_HANDLER(pmf) \
static_cast<wxPrivate::RemoveNoexceptEventHandler<decltype(pmf)>::type>(pmf)
#define wxEventHandlerNoexceptCast(functype, pmf) \
wxPrivate::DoCast<wxPrivate::EventArgOf<functype>::type>(pmf)
#else
#define wxREMOVE_NOEXCEPT_EVENT_HANDLER(pmf) pmf
#define wxEventHandlerNoexceptCast(functype, pmf) static_cast<functype>(pmf)
#endif
// Try to cast the given event handler to the correct handler type:
#define wxEVENT_HANDLER_CAST( functype, func ) \
wxEventFunctionCast(static_cast<functype>(wxREMOVE_NOEXCEPT_EVENT_HANDLER(&func)))
wxEventFunctionCast(wxEventHandlerNoexceptCast(functype, &func))
// The tag is a type associated to the event type (which is an integer itself,

View File

@@ -101,6 +101,9 @@ public:
void OnEvent(wxEvent&) { g_called.method = true; }
void OnAnotherEvent(AnotherEvent&);
void OnIdle(wxIdleEvent&) { g_called.method = true; }
void OnOverloadedHandler(wxIdleEvent&) { }
void OnOverloadedHandler(wxThreadEvent&) { }
};
// we can also handle events in classes not deriving from wxEvtHandler
@@ -203,6 +206,14 @@ TEST_CASE("Event::LegacyConnect", "[event][connect]")
handler.Disconnect( 0, 0, LegacyEventType, (wxObjectEventFunction)&MyHandler::OnEvent, NULL, &handler );
}
TEST_CASE("Event::ConnectOverloaded", "[event][connect]")
{
MyHandler handler;
handler.Connect(wxEVT_IDLE, wxIdleEventHandler(MyHandler::OnOverloadedHandler));
handler.Connect(wxEVT_THREAD, wxThreadEventHandler(MyHandler::OnOverloadedHandler));
}
TEST_CASE("Event::DisconnectWildcard", "[event][connect][disconnect]")
{
MyHandler handler;