From 1ef305de158498ba58063b19a02e40c9f6348857 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tor=20Arne=20Vestb=C3=B8?= Date: Tue, 13 Jul 2021 23:52:13 +0200 Subject: Improve error reporting when requesting unsupported native interface By switching out the static_assert for an enable_if we end up producing a clearer error, at the call site: /qt/qtbase/examples/gui/rasterwindow/main.cpp:69:9: error: no matching member function for call to 'nativeInterface' app.nativeInterface(); ~~~~^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ /qt/qtbase/src/gui/kernel/qguiapplication.h:176:5: note: candidate template ignored: requirement 'NativeInterface::isCompatibleWith' was not satisfied [with NativeInterface = QNativeInterface::QCocoaGLContext, TypeInfo = QNativeInterface::Private::NativeInterface, BaseType = QGuiApplication] QT_DECLARE_NATIVE_INTERFACE_ACCESSOR(QGuiApplication) ^ By using SFINAE for the TypeInfo we can also ensure that it works for types that are not native interfaces, such as if the user tries to call nativeInterface(). Since we can no longer use decltype(*this) to resolve the base type we need to change QT_DECLARE_NATIVE_INTERFACE_ACCESSOR to take the type as an argument, as we do for other QT_DECLARE_FOO macros. Pick-to: 6.2 Change-Id: Ie3f7e01ab7c3eb3dcc2ef730834f268bb9e81e0c Reviewed-by: Qt CI Bot Reviewed-by: Fabian Kosmale --- src/corelib/global/qnativeinterface.h | 108 ++++++++++++++++++++++++++++------ 1 file changed, 91 insertions(+), 17 deletions(-) (limited to 'src/corelib/global') diff --git a/src/corelib/global/qnativeinterface.h b/src/corelib/global/qnativeinterface.h index d32e2cd982..395a4ba1da 100644 --- a/src/corelib/global/qnativeinterface.h +++ b/src/corelib/global/qnativeinterface.h @@ -61,11 +61,21 @@ QT_BEGIN_NAMESPACE #define QT_DECLARE_NATIVE_INTERFACE_3(NativeInterface, Revision, BaseType) \ protected: \ virtual ~NativeInterface(); \ + \ struct TypeInfo { \ using baseType = BaseType; \ static constexpr char const *name = QT_STRINGIFY(NativeInterface); \ static constexpr int revision = Revision; \ }; \ + \ + template \ + friend struct QNativeInterface::Private::has_type_info; \ + \ + template \ + friend bool constexpr QNativeInterface::Private::hasTypeInfo(); \ + \ + template \ + friend struct QNativeInterface::Private::TypeInfo; \ public: \ // Revisioned interfaces only make sense when exposed through a base @@ -81,35 +91,97 @@ QT_BEGIN_NAMESPACE QT_OVERLOADED_MACRO(QT_DECLARE_NATIVE_INTERFACE, __VA_ARGS__) namespace QNativeInterface::Private { + + // Basic type-trait to verify that a given native interface has + // all the required type information for us to evaluate it. + template + struct has_type_info : std::false_type {}; + + // The type-trait is friended by TypeInfo, so that we can + // evaluate TypeInfo in the template arguments. + template + struct has_type_info> : std::true_type {}; + + // We need to wrap the instantiation of has_type_info in a + // function friended by TypeInfo, otherwise MSVC will not + // let us evaluate TypeInfo in the template arguments. template - struct TypeInfo : private NativeInterface + bool constexpr hasTypeInfo() { - static constexpr char const *name() { return NativeInterface::TypeInfo::name; } - static constexpr int revision() { return NativeInterface::TypeInfo::revision; } + return has_type_info::value; + } - template - static constexpr bool isCompatibleWith = - std::is_base_of::value; + template + struct TypeInfo + { + // To ensure SFINAE works for hasTypeInfo we can't use it in a constexpr + // variable that also includes an expression that relies on the type + // info. This helper variable is okey, as it it self contained. + static constexpr bool haveTypeInfo = hasTypeInfo(); + + // We can then use the helper variable in a constexpr condition in a + // function, which does not break SFINAE if haveTypeInfo is false. + template + static constexpr bool isCompatibleHelper() + { + if constexpr (haveTypeInfo) + return std::is_base_of::value; + else + return false; + } + + // MSVC however doesn't like constexpr functions in enable_if_t conditions, + // so we need to wrap it yet again in a constexpr variable. This is fine, + // as all the SFINAE magic has been resolved at this point. + template + static constexpr bool isCompatibleWith = isCompatibleHelper(); + + // The revision and name accessors are not used in enable_if_t conditions, + // so we can leave them as constexpr functions. As this class template is + // friended by TypeInfo we can access the protected members of TypeInfo. + static constexpr int revision() + { + if constexpr (haveTypeInfo) + return NativeInterface::TypeInfo::revision; + else + return 0; + } + + static constexpr char const *name() + { + if constexpr (haveTypeInfo) + return NativeInterface::TypeInfo::name; + else + return nullptr; + } }; + // Wrapper type to make the error message in case + // of incompatible interface types read better. + template + struct NativeInterface : TypeInfo {}; + template Q_NATIVE_INTERFACE_IMPORT void *resolveInterface(const T *that, const char *name, int revision); Q_CORE_EXPORT Q_DECLARE_LOGGING_CATEGORY(lcNativeInterface) -} + +} // QNativeInterface::Private // Declares an accessor for the native interface -#define QT_DECLARE_NATIVE_INTERFACE_ACCESSOR \ - template \ - I *nativeInterface() const \ +#define QT_DECLARE_NATIVE_INTERFACE_ACCESSOR(T) \ + template , \ + typename BaseType = T, std::enable_if_t, bool> = true> \ + NativeInterface *nativeInterface() const \ { \ - using T = std::decay_t; \ - using NativeInterface = QNativeInterface::Private::TypeInfo; \ - static_assert(NativeInterface::template isCompatibleWith, \ - "T::nativeInterface() requires that native interface I is compatible with T"); \ - \ - return static_cast(QNativeInterface::Private::resolveInterface(this, \ - NativeInterface::name(), NativeInterface::revision())); \ + return static_cast( \ + QNativeInterface::Private::resolveInterface(this, \ + TypeInfo::name(), TypeInfo::revision())); \ } // Provides a definition for the interface destructor @@ -134,6 +206,8 @@ namespace QNativeInterface::Private { revision, TypeInfo::revision(), name); \ return nullptr; \ } \ + } else { \ + qCDebug(lcNativeInterface, "No match for requested interface name %s", name); \ } QT_END_NAMESPACE -- cgit v1.2.3