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>
This commit is contained in:
Giuseppe D'Angelo 2020-03-24 08:47:00 +01:00
parent 76e54a2e15
commit bb0a616260
2 changed files with 93 additions and 250 deletions

View File

@ -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>;
struct NarrowingDetector { T t[1]; }; // from P0608
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)))))
>
{
};
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 }; };

View File

@ -6967,14 +6967,13 @@ void tst_QObject::mutableFunctor()
void tst_QObject::checkArgumentsForNarrowing()
{
enum UnscopedEnum {};
enum SignedUnscopedEnum { SignedUnscopedEnumV1 = -1, SignedUnscopedEnumV2 = 1 };
enum UnscopedEnum { UnscopedEnumV1 = INT_MAX, UnscopedEnumV2 };
enum SignedUnscopedEnum { SignedUnscopedEnumV1 = INT_MIN, SignedUnscopedEnumV2 = INT_MAX };
// a constexpr would suffice, but MSVC2013 RTM doesn't support them...
#define IS_UNSCOPED_ENUM_SIGNED (std::is_signed<typename std::underlying_type<UnscopedEnum>::type>::value)
static constexpr bool IsUnscopedEnumSigned = std::is_signed_v<std::underlying_type_t<UnscopedEnum>>;
#define NARROWS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsNarrowedBase<x, y>::value) == (test))
#define FITS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsNarrowedBase<x, y>::value) != (test))
#define NARROWS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<x, y>::value) != (test))
#define FITS_IF(x, y, test) Q_STATIC_ASSERT((QtPrivate::AreArgumentsConvertibleWithoutNarrowingBase<x, y>::value) == (test))
#define NARROWS(x, y) NARROWS_IF(x, y, true)
#define FITS(x, y) FITS_IF(x, y, true)
@ -6982,9 +6981,14 @@ void tst_QObject::checkArgumentsForNarrowing()
Q_STATIC_ASSERT(sizeof(SignedUnscopedEnum) <= sizeof(int));
// floating point to integral
// GCC < 9 does not consider floating point to bool to be narrowing,
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=65043
#if !defined(Q_CC_GNU) || Q_CC_GNU >= 900
NARROWS(float, bool);
NARROWS(double, bool);
NARROWS(long double, bool);
#endif
NARROWS(float, char);
NARROWS(double, char);
@ -7008,12 +7012,19 @@ void tst_QObject::checkArgumentsForNarrowing()
// floating point to a smaller floating point
NARROWS_IF(double, float, (sizeof(double) > sizeof(float)));
NARROWS_IF(long double, float, (sizeof(long double) > sizeof(float)));
NARROWS(double, float);
NARROWS(long double, float);
FITS(float, double);
FITS(float, long double);
NARROWS_IF(long double, double, (sizeof(long double) > sizeof(double)));
// GCC thinks this is narrowing only on architectures where
// sizeof(long double) > sizeof(double)
// https://gcc.gnu.org/bugzilla/show_bug.cgi?id=92856
#if defined(Q_CC_GNU)
NARROWS_IF(long double, double, sizeof(long double) > sizeof(double));
#else
NARROWS(long double, double);
#endif
FITS(double, long double);
@ -7202,21 +7213,21 @@ void tst_QObject::checkArgumentsForNarrowing()
FITS(UnscopedEnum, UnscopedEnum);
FITS(SignedUnscopedEnum, SignedUnscopedEnum);
NARROWS_IF(UnscopedEnum, char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && IS_UNSCOPED_ENUM_SIGNED == std::is_signed<char>::value)));
NARROWS_IF(UnscopedEnum, signed char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && !IS_UNSCOPED_ENUM_SIGNED)));
NARROWS_IF(UnscopedEnum, unsigned char, ((sizeof(UnscopedEnum) > sizeof(char)) || IS_UNSCOPED_ENUM_SIGNED));
NARROWS_IF(UnscopedEnum, char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && IsUnscopedEnumSigned == std::is_signed<char>::value)));
NARROWS_IF(UnscopedEnum, signed char, ((sizeof(UnscopedEnum) > sizeof(char)) || (sizeof(UnscopedEnum) == sizeof(char) && !IsUnscopedEnumSigned)));
NARROWS_IF(UnscopedEnum, unsigned char, ((sizeof(UnscopedEnum) > sizeof(char)) || IsUnscopedEnumSigned));
NARROWS_IF(UnscopedEnum, short, ((sizeof(UnscopedEnum) > sizeof(short)) || (sizeof(UnscopedEnum) == sizeof(short) && !IS_UNSCOPED_ENUM_SIGNED)));
NARROWS_IF(UnscopedEnum, unsigned short, ((sizeof(UnscopedEnum) > sizeof(short)) || IS_UNSCOPED_ENUM_SIGNED));
NARROWS_IF(UnscopedEnum, short, ((sizeof(UnscopedEnum) > sizeof(short)) || (sizeof(UnscopedEnum) == sizeof(short) && !IsUnscopedEnumSigned)));
NARROWS_IF(UnscopedEnum, unsigned short, ((sizeof(UnscopedEnum) > sizeof(short)) || IsUnscopedEnumSigned));
NARROWS_IF(UnscopedEnum, int, (sizeof(UnscopedEnum) == sizeof(int) && !IS_UNSCOPED_ENUM_SIGNED));
NARROWS_IF(UnscopedEnum, unsigned int, IS_UNSCOPED_ENUM_SIGNED);
NARROWS_IF(UnscopedEnum, int, sizeof(UnscopedEnum) > sizeof(int) || (sizeof(UnscopedEnum) == sizeof(int) && !IsUnscopedEnumSigned));
NARROWS_IF(UnscopedEnum, unsigned int, IsUnscopedEnumSigned);
NARROWS_IF(UnscopedEnum, long, (sizeof(UnscopedEnum) == sizeof(long) && !IS_UNSCOPED_ENUM_SIGNED));
NARROWS_IF(UnscopedEnum, unsigned long, IS_UNSCOPED_ENUM_SIGNED);
NARROWS_IF(UnscopedEnum, long, sizeof(UnscopedEnum) > sizeof(long) || (sizeof(UnscopedEnum) == sizeof(long) && !IsUnscopedEnumSigned));
NARROWS_IF(UnscopedEnum, unsigned long, IsUnscopedEnumSigned);
NARROWS_IF(UnscopedEnum, long long, (sizeof(UnscopedEnum) == sizeof(long long) && !IS_UNSCOPED_ENUM_SIGNED));
NARROWS_IF(UnscopedEnum, unsigned long long, IS_UNSCOPED_ENUM_SIGNED);
NARROWS_IF(UnscopedEnum, long long, sizeof(UnscopedEnum) > sizeof(long long) || (sizeof(UnscopedEnum) == sizeof(long long) && !IsUnscopedEnumSigned));
NARROWS_IF(UnscopedEnum, unsigned long long, IsUnscopedEnumSigned);
Q_STATIC_ASSERT(std::is_signed<typename std::underlying_type<SignedUnscopedEnum>::type>::value);
@ -7226,203 +7237,58 @@ void tst_QObject::checkArgumentsForNarrowing()
FITS(SignedUnscopedEnum, long);
FITS(SignedUnscopedEnum, long long);
enum class ScopedEnumBackedBySChar : signed char { A };
enum class ScopedEnumBackedByUChar : unsigned char { A };
enum class ScopedEnumBackedByShort : short { A };
enum class ScopedEnumBackedByUShort : unsigned short { A };
enum class ScopedEnumBackedByInt : int { A };
enum class ScopedEnumBackedByUInt : unsigned int { A };
enum class ScopedEnumBackedByLong : long { A };
enum class ScopedEnumBackedByULong : unsigned long { A };
enum class ScopedEnumBackedByLongLong : long long { A };
enum class ScopedEnumBackedByULongLong : unsigned long long { A };
FITS(ScopedEnumBackedBySChar, ScopedEnumBackedBySChar);
FITS(ScopedEnumBackedByUChar, ScopedEnumBackedByUChar);
FITS(ScopedEnumBackedByShort, ScopedEnumBackedByShort);
FITS(ScopedEnumBackedByUShort, ScopedEnumBackedByUShort);
FITS(ScopedEnumBackedByInt, ScopedEnumBackedByInt);
FITS(ScopedEnumBackedByUInt, ScopedEnumBackedByUInt);
FITS(ScopedEnumBackedByLong, ScopedEnumBackedByLong);
FITS(ScopedEnumBackedByULong, ScopedEnumBackedByULong);
FITS(ScopedEnumBackedByLongLong, ScopedEnumBackedByLongLong);
FITS(ScopedEnumBackedByULongLong, ScopedEnumBackedByULongLong);
FITS(ScopedEnumBackedBySChar, signed char);
FITS(ScopedEnumBackedByUChar, unsigned char);
FITS(ScopedEnumBackedByShort, short);
FITS(ScopedEnumBackedByUShort, unsigned short);
FITS(ScopedEnumBackedByInt, int);
FITS(ScopedEnumBackedByUInt, unsigned int);
FITS(ScopedEnumBackedByLong, long);
FITS(ScopedEnumBackedByULong, unsigned long);
FITS(ScopedEnumBackedByLongLong, long long);
FITS(ScopedEnumBackedByULongLong, unsigned long long);
FITS(ScopedEnumBackedBySChar, signed char);
FITS(ScopedEnumBackedBySChar, short);
FITS(ScopedEnumBackedBySChar, int);
FITS(ScopedEnumBackedBySChar, long);
FITS(ScopedEnumBackedBySChar, long long);
FITS(ScopedEnumBackedByUChar, unsigned char);
FITS(ScopedEnumBackedByUChar, unsigned short);
FITS(ScopedEnumBackedByUChar, unsigned int);
FITS(ScopedEnumBackedByUChar, unsigned long);
FITS(ScopedEnumBackedByUChar, unsigned long long);
NARROWS_IF(ScopedEnumBackedByShort, char, (sizeof(short) > sizeof(char) || std::is_unsigned<char>::value));
NARROWS_IF(ScopedEnumBackedByUShort, char, (sizeof(short) > sizeof(char) || std::is_signed<char>::value));
NARROWS_IF(ScopedEnumBackedByInt, char, (sizeof(int) > sizeof(char) || std::is_unsigned<char>::value));
NARROWS_IF(ScopedEnumBackedByUInt, char, (sizeof(int) > sizeof(char) || std::is_signed<char>::value));
NARROWS_IF(ScopedEnumBackedByLong, char, (sizeof(long) > sizeof(char) || std::is_unsigned<char>::value));
NARROWS_IF(ScopedEnumBackedByULong, char, (sizeof(long) > sizeof(char) || std::is_signed<char>::value));
NARROWS_IF(ScopedEnumBackedByLongLong, char, (sizeof(long long) > sizeof(char) || std::is_unsigned<char>::value));
NARROWS_IF(ScopedEnumBackedByULongLong, char, (sizeof(long long) > sizeof(char) || std::is_signed<char>::value));
NARROWS_IF(ScopedEnumBackedByShort, signed char, (sizeof(short) > sizeof(char)));
NARROWS(ScopedEnumBackedByUShort, signed char);
NARROWS_IF(ScopedEnumBackedByInt, signed char, (sizeof(int) > sizeof(char)));
NARROWS(ScopedEnumBackedByUInt, signed char);
NARROWS_IF(ScopedEnumBackedByLong, signed char, (sizeof(long) > sizeof(char)));
NARROWS(ScopedEnumBackedByULong, signed char);
NARROWS_IF(ScopedEnumBackedByLongLong, signed char, (sizeof(long long) > sizeof(char)));
NARROWS(ScopedEnumBackedByULongLong, signed char);
NARROWS(ScopedEnumBackedByShort, unsigned char);
NARROWS_IF(ScopedEnumBackedByUShort, unsigned char, (sizeof(short) > sizeof(char)));
NARROWS(ScopedEnumBackedByInt, unsigned char);
NARROWS_IF(ScopedEnumBackedByUInt, unsigned char, (sizeof(int) > sizeof(char)));
NARROWS(ScopedEnumBackedByLong, unsigned char);
NARROWS_IF(ScopedEnumBackedByULong, unsigned char, (sizeof(long) > sizeof(char)));
NARROWS(ScopedEnumBackedByLongLong, unsigned char);
NARROWS_IF(ScopedEnumBackedByULongLong, unsigned char, (sizeof(long long) > sizeof(char)));
NARROWS_IF(ScopedEnumBackedByInt, short, (sizeof(int) > sizeof(short)));
NARROWS(ScopedEnumBackedByUInt, short);
NARROWS_IF(ScopedEnumBackedByLong, short, (sizeof(long) > sizeof(short)));
NARROWS(ScopedEnumBackedByULong, short);
NARROWS_IF(ScopedEnumBackedByLongLong, short, (sizeof(long long) > sizeof(short)));
NARROWS(ScopedEnumBackedByULongLong, short);
NARROWS(ScopedEnumBackedByInt, unsigned short);
NARROWS_IF(ScopedEnumBackedByUInt, unsigned short, (sizeof(int) > sizeof(short)));
NARROWS(ScopedEnumBackedByLong, unsigned short);
NARROWS_IF(ScopedEnumBackedByULong, unsigned short, (sizeof(long) > sizeof(short)));
NARROWS(ScopedEnumBackedByLongLong, unsigned short);
NARROWS_IF(ScopedEnumBackedByULongLong, unsigned short, (sizeof(long long) > sizeof(short)));
NARROWS_IF(ScopedEnumBackedByLong, int, (sizeof(long) > sizeof(int)));
NARROWS(ScopedEnumBackedByULong, int);
NARROWS_IF(ScopedEnumBackedByLongLong, int, (sizeof(long long) > sizeof(int)));
NARROWS(ScopedEnumBackedByULongLong, int);
NARROWS(ScopedEnumBackedByLong, unsigned int);
NARROWS_IF(ScopedEnumBackedByULong, unsigned int, (sizeof(long) > sizeof(int)));
NARROWS(ScopedEnumBackedByLongLong, unsigned int);
NARROWS_IF(ScopedEnumBackedByULongLong, unsigned int, (sizeof(long long) > sizeof(int)));
NARROWS_IF(ScopedEnumBackedByLongLong, long, (sizeof(long long) > sizeof(long)));
NARROWS(ScopedEnumBackedByULongLong, long);
NARROWS(ScopedEnumBackedByLongLong, unsigned long);
NARROWS_IF(ScopedEnumBackedByULongLong, unsigned long, (sizeof(long long) > sizeof(long)));
// different signedness of the underlying type
NARROWS(SignedUnscopedEnum, unsigned char);
NARROWS(SignedUnscopedEnum, unsigned short);
NARROWS(SignedUnscopedEnum, unsigned int);
NARROWS(SignedUnscopedEnum, unsigned long);
NARROWS(SignedUnscopedEnum, unsigned long long);
NARROWS(ScopedEnumBackedBySChar, unsigned char);
NARROWS(ScopedEnumBackedBySChar, unsigned short);
NARROWS(ScopedEnumBackedBySChar, unsigned int);
NARROWS(ScopedEnumBackedBySChar, unsigned long);
NARROWS(ScopedEnumBackedBySChar, unsigned long long);
NARROWS(ScopedEnumBackedByShort, unsigned char);
NARROWS(ScopedEnumBackedByShort, unsigned short);
NARROWS(ScopedEnumBackedByShort, unsigned int);
NARROWS(ScopedEnumBackedByShort, unsigned long);
NARROWS(ScopedEnumBackedByShort, unsigned long long);
NARROWS(ScopedEnumBackedByInt, unsigned char);
NARROWS(ScopedEnumBackedByInt, unsigned short);
NARROWS(ScopedEnumBackedByInt, unsigned int);
NARROWS(ScopedEnumBackedByInt, unsigned long);
NARROWS(ScopedEnumBackedByInt, unsigned long long);
NARROWS(ScopedEnumBackedByLong, unsigned char);
NARROWS(ScopedEnumBackedByLong, unsigned short);
NARROWS(ScopedEnumBackedByLong, unsigned int);
NARROWS(ScopedEnumBackedByLong, unsigned long);
NARROWS(ScopedEnumBackedByLong, unsigned long long);
NARROWS(ScopedEnumBackedByLongLong, unsigned char);
NARROWS(ScopedEnumBackedByLongLong, unsigned short);
NARROWS(ScopedEnumBackedByLongLong, unsigned int);
NARROWS(ScopedEnumBackedByLongLong, unsigned long);
NARROWS(ScopedEnumBackedByLongLong, unsigned long long);
NARROWS(ScopedEnumBackedByUChar, signed char);
FITS_IF(ScopedEnumBackedByUChar, short, (sizeof(char) < sizeof(short)));
FITS_IF(ScopedEnumBackedByUChar, int, (sizeof(char) < sizeof(int)));
FITS_IF(ScopedEnumBackedByUChar, long, (sizeof(char) < sizeof(long)));
FITS_IF(ScopedEnumBackedByUChar, long long, (sizeof(char) < sizeof(long long)));
NARROWS(ScopedEnumBackedByUShort, signed char);
NARROWS(ScopedEnumBackedByUShort, short);
FITS_IF(ScopedEnumBackedByUShort, int, (sizeof(short) < sizeof(int)));
FITS_IF(ScopedEnumBackedByUShort, long, (sizeof(short) < sizeof(long)));
FITS_IF(ScopedEnumBackedByUShort, long long, (sizeof(short) < sizeof(long long)));
NARROWS(ScopedEnumBackedByUInt, signed char);
NARROWS(ScopedEnumBackedByUInt, short);
NARROWS(ScopedEnumBackedByUInt, int);
FITS_IF(ScopedEnumBackedByUInt, long, (sizeof(ScopedEnumBackedByUInt) < sizeof(long)));
FITS(ScopedEnumBackedByUInt, long long);
NARROWS(ScopedEnumBackedByULong, signed char);
NARROWS(ScopedEnumBackedByULong, short);
NARROWS(ScopedEnumBackedByULong, int);
NARROWS(ScopedEnumBackedByULong, long);
FITS_IF(ScopedEnumBackedByULong, long long, (sizeof(ScopedEnumBackedByULong) < sizeof(long long)));
NARROWS(ScopedEnumBackedByULongLong, signed char);
NARROWS(ScopedEnumBackedByULongLong, short);
NARROWS(ScopedEnumBackedByULongLong, int);
NARROWS(ScopedEnumBackedByULongLong, long);
NARROWS(ScopedEnumBackedByULongLong, long long);
// other types which should be always unaffected
FITS(void *, void *);
FITS(QString, QString);
FITS(QString &, QString &);
FITS(const QString &, const QString &);
FITS(QObject, QObject);
FITS(QObject *, QObject *);
FITS(const QObject *, const QObject *);
FITS(std::nullptr_t, std::nullptr_t);
FITS(QString, QObject);
FITS(QString, QVariant);
FITS(QString, void *);
FITS(QString, long long);
FITS(bool, const QObject *&);
FITS(int (*)(bool), void (QObject::*)());
// classes with conversion operators undergoing implicit conversions
struct ConvertingToDouble {
/* implicit */ operator double() const { return 42.0; }
};
NARROWS(ConvertingToDouble, char);
NARROWS(ConvertingToDouble, short);
NARROWS(ConvertingToDouble, int);
NARROWS(ConvertingToDouble, long);
NARROWS(ConvertingToDouble, long long);
NARROWS(ConvertingToDouble, float);
FITS(ConvertingToDouble, double);
FITS(ConvertingToDouble, long double);
// no compiler still implements this properly.
#if 0
struct ConstructibleFromInt {
/* implicit */ ConstructibleFromInt(int) {}
};
FITS(char, ConstructibleFromInt);
FITS(short, ConstructibleFromInt);
FITS(int, ConstructibleFromInt);
NARROWS(unsigned int, ConstructibleFromInt);
NARROWS_IF(long, ConstructibleFromInt, sizeof(long) > sizeof(int));
NARROWS_IF(long long, ConstructibleFromInt, sizeof(long long) > sizeof(int));
NARROWS(float, ConstructibleFromInt);
NARROWS(double, ConstructibleFromInt);
#endif
// forward declared classes must work
class ForwardDeclared;
FITS(ForwardDeclared, ForwardDeclared);
#if 0 // waiting for official compiler releases that implement P1957...
{
// wg21.link/P1957
NARROWS(char*, bool);
NARROWS(void (QObject::*)(), bool);
}
#undef IS_UNSCOPED_ENUM_SIGNED
#endif
#undef NARROWS_IF
#undef FITS_IF