summaryrefslogtreecommitdiffstats
path: root/src/corelib/kernel/qobjectdefs_impl.h
diff options
context:
space:
mode:
authorGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2020-03-24 08:47:00 +0100
committerGiuseppe D'Angelo <giuseppe.dangelo@kdab.com>2020-04-15 15:20:45 +0200
commitbb0a6162608fe932f8534bbe65594c6bf894867a (patch)
tree2f5569bf59ed7180f7fc6e558c9292e782e7937a /src/corelib/kernel/qobjectdefs_impl.h
parent76e54a2e151a7d31e18feb84cc79451724ab7c6f (diff)
QObject: overhaul narrowing detection
Use P0608's trick to detect convertibility without narrowing; and now that we can depend on C++17, use its features. First, this moves the burden of detecting a narrowing conversion on the compiler, rather than us maintaining a complicated series of checks. Of course, this exposes * bugs in compilers (e.g. GCC < 9 thinks that float->bool is not narrowing; * behavior still not (widely) implemented (pointer to bool conversions are narrowing, P1957); * interesting compiler choices, e.g. GCC 9 thinks that unscoped enumerations are non-narrowing convertible to a datatype big enum to contain all the _enumerators_, even if the underlying type of the enum (and/or its sizeof()) is wider than the target datatype. Second, it allows to detect conversions that have a narrowing conversion as an intermediate step. Given a type like struct Bad { operator double() const; }; then an object of type Bad is implictly convertible to a type like int via a narrowing conversion. Therefore, a connection is possible between a signal carrying a Bad and a slot accepting an int. We can now detect and block this. Tests regarding scoped enumerations have been dropped, for the simple reason that a scoped enumeration is not implictly convertible to an integral type, so we don't have that detection (it would constantly fail). Scoped enumerations do not take part in narrowing conversions anyhow, cf. [dcl.init.list]. [ChangeLog][QtCore][QObject] The detection of narrowing conversions when calling QObject::connect() when QT_NO_NARROWING_CONVERSIONS_IN_CONNECT now takes also into account user-defined implicit conversions that undergo through a narrowing conversion. Change-Id: Ie09d59203fe6283378b36dfbc54de1d58098ef51 Reviewed-by: Volker Hilsheimer <volker.hilsheimer@qt.io> Reviewed-by: Lars Knoll <lars.knoll@qt.io>
Diffstat (limited to 'src/corelib/kernel/qobjectdefs_impl.h')
-rw-r--r--src/corelib/kernel/qobjectdefs_impl.h77
1 files changed, 27 insertions, 50 deletions
diff --git a/src/corelib/kernel/qobjectdefs_impl.h b/src/corelib/kernel/qobjectdefs_impl.h
index aed50d6c5a..0624dd2584 100644
--- a/src/corelib/kernel/qobjectdefs_impl.h
+++ b/src/corelib/kernel/qobjectdefs_impl.h
@@ -257,55 +257,32 @@ namespace QtPrivate {
}
};
- /*
- Logic that checks if the underlying type of an enum is signed or not.
- Needs an external, explicit check that E is indeed an enum. Works
- around the fact that it's undefined behavior to instantiate
- std::underlying_type on non-enums (cf. §20.13.7.6 [meta.trans.other]).
- */
- template<typename E, typename Enable = void>
- struct IsEnumUnderlyingTypeSigned : std::false_type
- {
- };
-
- template<typename E>
- struct IsEnumUnderlyingTypeSigned<E, typename std::enable_if<std::is_enum<E>::value>::type>
- : std::integral_constant<bool, std::is_signed<typename std::underlying_type<E>::type>::value>
- {
- };
-
- /*
- Logic that checks if the argument of the slot does not narrow the
- argument of the signal when used in list initialization. Cf. §8.5.4.7
- [dcl.init.list] for the definition of narrowing.
- For incomplete From/To types, there's no narrowing.
- */
- template<typename From, typename To, typename Enable = void>
- struct AreArgumentsNarrowedBase : std::false_type
- {
- };
-
+ // Traits to detect if there is a conversion between two types,
+ // and that conversion does not include a narrowing conversion.
template <typename T>
- using is_bool = std::is_same<bool, typename std::decay<T>::type>;
-
- template<typename From, typename To>
- struct AreArgumentsNarrowedBase<From, To, typename std::enable_if<sizeof(From) && sizeof(To)>::type>
- : std::integral_constant<bool,
- (std::is_floating_point<From>::value && std::is_integral<To>::value) ||
- (std::is_floating_point<From>::value && std::is_floating_point<To>::value && sizeof(From) > sizeof(To)) ||
- ((std::is_pointer<From>::value || std::is_member_pointer<From>::value) && QtPrivate::is_bool<To>::value) ||
- ((std::is_integral<From>::value || std::is_enum<From>::value) && std::is_floating_point<To>::value) ||
- (std::is_integral<From>::value && std::is_integral<To>::value
- && (sizeof(From) > sizeof(To)
- || (std::is_signed<From>::value ? !std::is_signed<To>::value
- : (std::is_signed<To>::value && sizeof(From) == sizeof(To))))) ||
- (std::is_enum<From>::value && std::is_integral<To>::value
- && (sizeof(From) > sizeof(To)
- || (IsEnumUnderlyingTypeSigned<From>::value ? !std::is_signed<To>::value
- : (std::is_signed<To>::value && sizeof(From) == sizeof(To)))))
- >
- {
- };
+ struct NarrowingDetector { T t[1]; }; // from P0608
+
+ template <typename From, typename To, typename Enable = void>
+ struct IsConvertibleWithoutNarrowing : std::false_type {};
+
+ template <typename From, typename To>
+ struct IsConvertibleWithoutNarrowing<From, To,
+ std::void_t< decltype( NarrowingDetector<To>{ {std::declval<From>()} } ) >
+ > : std::true_type {};
+
+ // Check for the actual arguments. If they are exactly the same,
+ // then don't bother checking for narrowing; as a by-product,
+ // this solves the problem of incomplete types (which must be supported,
+ // or they would error out in the trait above).
+ template <typename From, typename To, typename Enable = void>
+ struct AreArgumentsConvertibleWithoutNarrowingBase : std::false_type {};
+
+ template <typename From, typename To>
+ struct AreArgumentsConvertibleWithoutNarrowingBase<From, To,
+ std::enable_if_t<
+ std::disjunction_v<std::is_same<From, To>, IsConvertibleWithoutNarrowing<From, To>>
+ >
+ > : std::true_type {};
/*
Logic that check if the arguments of the slot matches the argument of the signal.
@@ -318,8 +295,8 @@ namespace QtPrivate {
static const typename RemoveRef<A1>::Type &dummy();
enum { value = sizeof(test(dummy())) == sizeof(int) };
#ifdef QT_NO_NARROWING_CONVERSIONS_IN_CONNECT
- using AreArgumentsNarrowed = AreArgumentsNarrowedBase<typename RemoveRef<A1>::Type, typename RemoveRef<A2>::Type>;
- Q_STATIC_ASSERT_X(!AreArgumentsNarrowed::value, "Signal and slot arguments are not compatible (narrowing)");
+ using AreArgumentsConvertibleWithoutNarrowing = AreArgumentsConvertibleWithoutNarrowingBase<std::decay_t<A1>, std::decay_t<A2>>;
+ Q_STATIC_ASSERT_X(AreArgumentsConvertibleWithoutNarrowing::value, "Signal and slot arguments are not compatible (narrowing)");
#endif
};
template<typename A1, typename A2> struct AreArgumentsCompatible<A1, A2&> { enum { value = false }; };