Long live 64-bit QFlags

This commit removes the limitation on size that QFlags used to have,
allowing up to 64 bits. We could increase to 128 bits at this time, but
I'm choosing not to allow this yet, due to limitations in handling
128-bit integers in QMetaObject & QMetaObjectBuilder. The API wouldn't
be cross-platform.

This commit duplicates the tst_QFlags into a new tst_QFlags64. It
restores the tst_QVariant state to how it was before (even though it
doesn't actually test the QMetaEnum functionality).

[ChangeLog][QtCore][QFlags] Now supports 64-bit enumerations, with the
same syntax, including extraction into QMetaObject using the Q_FLAG and
Q_DECLARE_FLAGS markers. Note the QFlag helper class remains 32-bit
wide. Creation of a 64-bit QFlags from a generic integer can be achieved
by passing a std::in_place first argument.

Fixes: QTBUG-111926
Change-Id: Ifb754f0e28774c20aa7cfffd17e7fc1d16baf8e4
Reviewed-by: Fabian Kosmale <fabian.kosmale@qt.io>
Reviewed-by: Ahmad Samir <a.samirh78@gmail.com>
This commit is contained in:
Thiago Macieira 2024-08-18 13:22:41 -07:00
parent 39bb305416
commit 2effeb25c0
7 changed files with 262 additions and 58 deletions

View File

@ -7,6 +7,7 @@
#include <QtCore/qcompare_impl.h>
#include <QtCore/qtypeinfo.h>
#include <algorithm>
#include <initializer_list>
QT_BEGIN_NAMESPACE
@ -51,38 +52,76 @@ constexpr inline QIncompatibleFlag::QIncompatibleFlag(int value) noexcept : i(va
namespace QtPrivate {
template <typename T> struct IsQFlags : std::false_type {};
template <typename E> struct IsQFlags<QFlags<E>> : std::true_type {};
template<typename Enum>
class QFlagsStorage
{
static_assert(sizeof(Enum) <= sizeof(quint64),
"Only enumerations 64 bits or smaller are supported.");
static_assert((std::is_enum<Enum>::value), "QFlags is only usable on enumeration types.");
static constexpr size_t IntegerSize = (std::max)(sizeof(Enum), sizeof(int));
using Integers = QIntegerForSize<IntegerSize>;
protected:
typedef typename std::conditional<
std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
typename Integers::Unsigned,
typename Integers::Signed
>::type Int;
Int i = 0;
public:
constexpr inline QFlagsStorage() noexcept = default;
constexpr inline explicit QFlagsStorage(std::in_place_t, Int v) : i(v) {}
};
template <typename Enum, int Size = sizeof(QFlagsStorage<Enum>)>
struct QFlagsStorageHelper : QFlagsStorage<Enum>
{
using QFlagsStorage<Enum>::QFlagsStorage;
};
template <typename Enum> struct QFlagsStorageHelper<Enum, sizeof(int)> : QFlagsStorage<Enum>
{
using QFlagsStorage<Enum>::QFlagsStorage;
// For compatibility with Qt 3, moc goes through QFlag in order to
// read/write properties of type QFlags; so a conversion to QFlag is also
// needed here. (It otherwise goes through a QFlags->int->QFlag conversion
// sequence.)
constexpr inline Q_IMPLICIT QFlagsStorageHelper(QFlag flag) noexcept
: QFlagsStorage<Enum>(std::in_place, flag) {}
#ifdef QT_TYPESAFE_FLAGS
constexpr inline explicit operator QFlag() const noexcept { return QFlag(this->i); }
#endif
};
} // namespace QtPrivate
template<typename Enum>
class QFlags
class QFlags : public QtPrivate::QFlagsStorageHelper<Enum>
{
static_assert((sizeof(Enum) <= sizeof(int)),
"QFlags uses an int as storage, so an enum with underlying "
"long long will overflow.");
static_assert((std::is_enum<Enum>::value), "QFlags is only usable on enumeration types.");
using Base = QtPrivate::QFlagsStorageHelper<Enum>;
public:
#if defined(Q_CC_MSVC) || defined(Q_QDOC)
// see above for MSVC
// the definition below is too complex for qdoc
typedef int Int;
#else
typedef typename std::conditional<
std::is_unsigned<typename std::underlying_type<Enum>::type>::value,
unsigned int,
signed int
>::type Int;
#endif
typedef Enum enum_type;
using Int = typename Base::Int;
using Base::Base;
// compiler-generated copy/move ctor/assignment operators are fine!
constexpr inline QFlags() noexcept : i(0) {}
constexpr inline Q_IMPLICIT QFlags(Enum flags) noexcept : i(Int(flags)) {}
constexpr inline Q_IMPLICIT QFlags(QFlag flag) noexcept : i(flag) {}
constexpr inline QFlags() noexcept = default;
constexpr inline Q_IMPLICIT QFlags(Enum flags) noexcept : Base(std::in_place, Int(flags)) {}
#ifdef Q_QDOC
constexpr inline Q_IMPLICIT QFlags(std::in_place_t, Int flags) noexcept;
constexpr inline Q_IMPLICIT QFlags(QFlag flag) noexcept
requires(sizeof(Enum) == sizeof(int));
#endif
constexpr inline QFlags(std::initializer_list<Enum> flags) noexcept
: i(initializer_list_helper(flags.begin(), flags.end())) {}
: Base(std::in_place, initializer_list_helper(flags.begin(), flags.end())) {}
constexpr static inline QFlags fromInt(Int i) noexcept { return QFlags(QFlag(i)); }
constexpr static inline QFlags fromInt(Int i) noexcept { return QFlags(std::in_place, i); }
constexpr inline Int toInt() const noexcept { return i; }
#ifndef QT_TYPESAFE_FLAGS
@ -99,27 +138,22 @@ public:
#ifdef QT_TYPESAFE_FLAGS
constexpr inline explicit operator Int() const noexcept { return i; }
constexpr inline explicit operator bool() const noexcept { return i; }
// For some reason, moc goes through QFlag in order to read/write
// properties of type QFlags; so a conversion to QFlag is also
// needed here. (It otherwise goes through a QFlags->int->QFlag
// conversion sequence.)
constexpr inline explicit operator QFlag() const noexcept { return QFlag(i); }
#else
constexpr inline Q_IMPLICIT operator Int() const noexcept { return i; }
constexpr inline bool operator!() const noexcept { return !i; }
#endif
constexpr inline QFlags operator|(QFlags other) const noexcept { return QFlags(QFlag(i | other.i)); }
constexpr inline QFlags operator|(Enum other) const noexcept { return QFlags(QFlag(i | Int(other))); }
constexpr inline QFlags operator^(QFlags other) const noexcept { return QFlags(QFlag(i ^ other.i)); }
constexpr inline QFlags operator^(Enum other) const noexcept { return QFlags(QFlag(i ^ Int(other))); }
constexpr inline QFlags operator|(QFlags other) const noexcept { return QFlags(std::in_place, i | other.i); }
constexpr inline QFlags operator|(Enum other) const noexcept { return QFlags(std::in_place, i | Int(other)); }
constexpr inline QFlags operator^(QFlags other) const noexcept { return QFlags(std::in_place, i ^ other.i); }
constexpr inline QFlags operator^(Enum other) const noexcept { return QFlags(std::in_place, i ^ Int(other)); }
#ifndef QT_TYPESAFE_FLAGS
constexpr inline QFlags operator&(int mask) const noexcept { return QFlags(QFlag(i & mask)); }
constexpr inline QFlags operator&(uint mask) const noexcept { return QFlags(QFlag(i & mask)); }
constexpr inline QFlags operator&(int mask) const noexcept { return QFlags(std::in_place, i & mask); }
constexpr inline QFlags operator&(uint mask) const noexcept { return QFlags(std::in_place, i & mask); }
#endif
constexpr inline QFlags operator&(QFlags other) const noexcept { return QFlags(QFlag(i & other.i)); }
constexpr inline QFlags operator&(Enum other) const noexcept { return QFlags(QFlag(i & Int(other))); }
constexpr inline QFlags operator~() const noexcept { return QFlags(QFlag(~i)); }
constexpr inline QFlags operator&(QFlags other) const noexcept { return QFlags(std::in_place, i & other.i); }
constexpr inline QFlags operator&(Enum other) const noexcept { return QFlags(std::in_place, i & Int(other)); }
constexpr inline QFlags operator~() const noexcept { return QFlags(std::in_place, ~i); }
constexpr inline void operator+(QFlags other) const noexcept = delete;
constexpr inline void operator+(Enum other) const noexcept = delete;
@ -174,7 +208,7 @@ private:
template <typename E> friend QDataStream &operator<<(QDataStream &, QFlags<E>);
template <typename E> friend QDataStream &operator>>(QDataStream &, QFlags<E> &);
Int i;
using Base::i;
};
#ifndef Q_MOC_RUN

View File

@ -72,6 +72,12 @@
enum value can be OR'd with any other enum value and passed on to
a function that takes an \c int or \c uint.
Since Qt 6.9, QFlags supports 64-bit enumerations. It is recommended to use
explicit (fixed) underlying types when going above 32 bits, to ensure
neither the enumeration nor the QFlags type change sizes if an enumerator
is removed. Some compilers will also only make the \c enum type larger than
32 bits with explicit underlying types.
Qt uses QFlags to provide type safety. For example, the
Qt::Alignment type is simply a typedef for
QFlags<Qt::AlignmentFlag>. QLabel::setAlignment() takes a
@ -112,8 +118,6 @@
When a singular name is desired for the QFlags type (e.g., \c
Alignment), you can use \c Flag as the suffix for the enum type
(e.g., \c AlignmentFlag).
\sa QFlag
*/
/*!
@ -121,8 +125,10 @@
\since 5.0
Typedef for the integer type used for storage as well as for
implicit conversion. Either \c int or \c{unsigned int}, depending
on whether the enum's underlying type is signed or unsigned.
implicit conversion. Either \c qintXX or \c quintXX, depending on
whether the enum's underlying type is signed or unsigned and, since Qt 6.9,
the enum's size. Typically, it will be \c qint32 (\c{int}) or \c quint32
(\c{unsigned}).
*/
/*!
@ -159,6 +165,16 @@
int, we effectively ensure that arbitrary enum values cannot be
cast to a QFlags, whereas untyped enum values (i.e., \c int
values) can.
This constructor is only present for 32-bit \c Enum types. To support all
enum sizes, consider the constructor using \c{std::in_place_t}.
*/
/*!
\fn template <typename Enum> QFlags<Enum>::QFlags(std::in_place_t, Int flags)
\since 6.9
Constructs a QFlags object initialized with the integer \a flags.
*/
/*!
@ -184,6 +200,12 @@
Performs a bitwise AND operation with \a mask and stores the
result in this QFlags object. Returns a reference to this object.
//! [unsafe-integer]
This operator is disabled if the \c{QT_TYPESAFE_FLAGS} macro is defined.
Note that it is not extended to 64-bit for 64-bit QFlags either: for 64-bit
support, use the type-safe overload.
//! [unsafe-integer]
\sa operator&(), operator|=(), operator^=()
*/
@ -191,6 +213,7 @@
\fn template <typename Enum> QFlags &QFlags<Enum>::operator&=(uint mask)
\overload
\include qflags.qdoc unsafe-integer
*/
/*!
@ -280,6 +303,8 @@
Returns a QFlags object containing the result of the bitwise AND
operation on this object and \a mask.
\include qflags.qdoc unsafe-integer
\sa operator&=(), operator|(), operator^(), operator~()
*/
@ -287,6 +312,7 @@
\fn template <typename Enum> QFlags QFlags<Enum>::operator&(uint mask) const
\overload
\include qflags.qdoc unsafe-integer
*/
/*!

View File

@ -579,7 +579,7 @@ inline QDebug operator<<(QDebug debug, Flags flags)
// mutually exclusive.
namespace QtPrivate {
template <typename T, bool IsEnum = std::is_enum_v<T>, bool SizedForQFlags = sizeof(T) <= 4>
template <typename T, bool IsEnum = std::is_enum_v<T>, bool = sizeof(T) <= sizeof(quint64)>
struct EnumHasQFlag { static constexpr bool Value = false; };
template <typename T> struct EnumHasQFlag<T, true, true> : QtPrivate::IsQEnumHelper<QFlags<T>> {};

View File

@ -2990,8 +2990,6 @@ QMetaType QMetaType::underlyingType() const
differentiate between different underlying types of the
same size and signedness (consider char <-> (un)signed char,
int <-> long <-> long long).
### TODO PENDING: QTBUG-111926 - QFlags supporting >32 bit int
*/
if (flags() & IsUnsignedEnumeration) {
switch (sizeOf()) {

View File

@ -16,6 +16,13 @@ qt_internal_add_test(tst_qflags
tst_qflags.cpp
)
qt_internal_add_test(tst_qflags64
SOURCES
tst_qflags.cpp
DEFINES
QFLAGS_TEST_64
)
qt_internal_add_test(tst_qflags_non_typesafe
SOURCES
tst_qflags.cpp

View File

@ -14,10 +14,22 @@
#include <QTest>
#if defined(__cpp_concepts) && __has_include(<concepts>)
# include <concepts>
#endif
#ifdef QFLAGS_TEST_64
# define tst_QFlags tst_QFlags64
using IntegerSize = QIntegerForSize<8>;
#else
using IntegerSize = QIntegerForSize<4>;
#endif
class tst_QFlags: public QObject
{
Q_OBJECT
private slots:
void construction() const;
void boolCasts() const;
void operators() const;
void mixingDifferentEnums() const;
@ -28,26 +40,25 @@ private slots:
void testAnyFlag();
void constExpr();
void signedness();
void size();
void castToFromQFlag();
void classEnum();
void initializerLists();
void testSetFlags();
void adl();
};
// untyped enum: even though we expect this to be signed, it's possible the
// compiler implements it as unsigned
enum SignedFlag {
enum SignedFlag : IntegerSize::Signed {
NoSignedFlag = 0x00000000,
LeftSignedFlag = 0x00000001,
RightSignedFlag = 0x00000002,
MiddleSignedFlag = 0x00000004,
SignedFlagMask = -1
SignedFlagMask = IntegerSize::Signed(-1)
};
Q_DECLARE_FLAGS(SignedFlags, SignedFlag)
Q_DECLARE_OPERATORS_FOR_FLAGS(SignedFlags)
// typed enum: this one we expect to be unsigned (but MSVC says it isn't)
enum UnsignedFlag : unsigned {
enum UnsignedFlag : IntegerSize::Unsigned {
UnsignedFlag01 = 0x0001,
UnsignedFlag02 = 0x0002,
UnsignedFlag04 = 0x0004,
@ -62,7 +73,18 @@ Q_DECLARE_OPERATORS_FOR_FLAGS(UnsignedFlags)
enum MixingFlag {
MixingFlag100 = 0x0100,
};
Q_DECLARE_MIXED_ENUM_OPERATORS_SYMMETRIC(int, UnsignedFlag, MixingFlag)
Q_DECLARE_MIXED_ENUM_OPERATORS_SYMMETRIC(IntegerSize::Signed, UnsignedFlag, MixingFlag)
void tst_QFlags::construction() const
{
UnsignedFlags def;
UnsignedFlags copied(def);
UnsignedFlags moved(std::move(copied)); Q_UNUSED(moved);
UnsignedFlags fromEnum(UnsignedFlag01); Q_UNUSED(fromEnum);
UnsignedFlags inPlace(std::in_place, 0xffff); Q_UNUSED(inPlace);
UnsignedFlags fromInt = UnsignedFlags::fromInt(0xffff); Q_UNUSED(fromInt);
// initializer_list tested in initializerLists()
}
void tst_QFlags::boolCasts() const
{
@ -294,7 +316,11 @@ void tst_QFlags::constExpr()
VERIFY_CONSTEXPR((LeftSignedFlag | RightSignedFlag) | MiddleSignedFlag, LeftSignedFlag | RightSignedFlag | MiddleSignedFlag);
VERIFY_CONSTEXPR(~(LeftSignedFlag | RightSignedFlag), ~(LeftSignedFlag | RightSignedFlag));
VERIFY_CONSTEXPR(SignedFlags(LeftSignedFlag) ^ RightSignedFlag, LeftSignedFlag ^ RightSignedFlag);
VERIFY_CONSTEXPR(SignedFlags{}, 0);
#ifndef tst_QFlags
// only works with QFlag help
VERIFY_CONSTEXPR(SignedFlags(0), 0);
#endif
#ifndef QT_TYPESAFE_FLAGS
VERIFY_CONSTEXPR(SignedFlags(RightSignedFlag) & 0xff, RightSignedFlag);
VERIFY_CONSTEXPR(SignedFlags(RightSignedFlag) | 0xff, 0xff);
@ -320,17 +346,119 @@ void tst_QFlags::signedness()
#endif
}
enum class MyStrictEnum { StrictZero, StrictOne, StrictTwo, StrictFour=4 };
enum class MyStrictEnum : IntegerSize::Unsigned
{ StrictZero, StrictOne, StrictTwo, StrictFour=4 };
Q_DECLARE_FLAGS( MyStrictFlags, MyStrictEnum )
Q_DECLARE_OPERATORS_FOR_FLAGS( MyStrictFlags )
enum class MyStrictNoOpEnum { StrictZero, StrictOne, StrictTwo, StrictFour=4 };
enum class MyStrictNoOpEnum : IntegerSize::Unsigned
{ StrictZero, StrictOne, StrictTwo, StrictFour=4 };
Q_DECLARE_FLAGS( MyStrictNoOpFlags, MyStrictNoOpEnum )
static_assert( !QTypeInfo<MyStrictFlags>::isComplex );
static_assert( QTypeInfo<MyStrictFlags>::isRelocatable );
static_assert( !std::is_pointer_v<MyStrictFlags> );
void tst_QFlags::size()
{
static_assert(sizeof(UnsignedFlags) >= sizeof(IntegerSize::Unsigned));
static_assert(sizeof(MyStrictFlags) == sizeof(IntegerSize::Unsigned));
}
template <typename Flags, typename FlagType> void castToFromQFlag_template()
{
// Verify that 32-bit QFlags works with QFlag and, through it,
// can be constructed from integer types.
auto testType = [](auto initialValue) {
using T = decltype(+initialValue);
FlagType flag(initialValue); // can construct QFlag from this type
T v1 = flag; // can cast QFlag to this type
Q_UNUSED(v1);
Flags flags(initialValue); // can construct QFlags through QFlag from this type
T v2 = QFlag(flags); // can cast QFlags to this type through QFlag
Q_UNUSED(v2);
};
testType(qint8(-1));
testType(char(1));
testType(uchar(2));
testType(short(3));
testType(ushort(4));
testType(int(5));
testType(uint(6));
#ifdef Q_CC_MSVC
// QFlag has a constructor for uint for all other compilers, which make
// the construction from long or ulong ambiguous.
testType(long(7));
testType(ulong(8));
#endif
FlagType flag(1);
IntegerSize::Signed i = flag; // must cast to integers
IntegerSize::Unsigned u = flag; // must cast to integers
QCOMPARE(i, 1);
QCOMPARE(u, 1U);
// QFlags has a constructor on QFlag
SignedFlags f = flag;
QCOMPARE(f, LeftSignedFlag);
UnsignedFlags uf = flag;
QCOMPARE(uf, UnsignedFlag01);
MyStrictFlags sf = flag;
QCOMPARE(sf, MyStrictEnum::StrictOne);
#ifndef Q_CC_MSVC
// QFlags has a cast operator to QFlag
// ### this used to work but began failing with MSVC after QFlagsStorage
// was introduced
flag = FlagType(f);
QCOMPARE(IntegerSize::Signed(flag), 1);
flag = FlagType(uf);
QCOMPARE(IntegerSize::Signed(flag), 1);
flag = FlagType(sf);
QCOMPARE(IntegerSize::Signed(flag), 1);
#endif
// and thus this should compile
QCOMPARE(f, 1);
QCOMPARE(uf, 1);
QCOMPARE(sf, 1);
}
template <typename Flags> void noCastToFromQFlag_template()
{
// Verify that non-32-bit QFlags doesn't have QFlag support
static_assert(!std::is_constructible_v<Flags, char>);
static_assert(!std::is_constructible_v<Flags, uchar>);
static_assert(!std::is_constructible_v<Flags, signed char>);
static_assert(!std::is_constructible_v<Flags, char16_t>);
static_assert(!std::is_constructible_v<Flags, char32_t>);
static_assert(!std::is_constructible_v<Flags, short>);
static_assert(!std::is_constructible_v<Flags, ushort>);
static_assert(!std::is_constructible_v<Flags, int>);
static_assert(!std::is_constructible_v<Flags, uint>);
static_assert(!std::is_constructible_v<Flags, long>);
static_assert(!std::is_constructible_v<Flags, ulong>);
static_assert(!std::is_constructible_v<Flags, qlonglong>);
static_assert(!std::is_constructible_v<Flags, qulonglong>);
static_assert(!std::is_constructible_v<Flags, QFlag>);
static_assert(!std::is_constructible_v<QFlag, Flags>);
#if defined(__cpp_concepts) && __has_include(<concepts>)
static_assert(!std::equality_comparable_with<QFlag, Flags>);
static_assert(!std::equality_comparable_with<Flags, int>);
#endif
}
void tst_QFlags::castToFromQFlag()
{
if constexpr (sizeof(IntegerSize::Signed) == sizeof(int)) {
castToFromQFlag_template<MyStrictFlags, QFlag>();
} else {
noCastToFromQFlag_template<MyStrictFlags>();
}
}
void tst_QFlags::classEnum()
{
// The main aim of the test is making sure it compiles
@ -476,7 +604,7 @@ void tst_QFlags::testSetFlags()
}
namespace SomeNS {
enum Foo { Foo_A = 1 << 0, Foo_B = 1 << 1, Foo_C = 1 << 2 };
enum Foo : IntegerSize::Unsigned { Foo_A = 1 << 0, Foo_B = 1 << 1, Foo_C = 1 << 2 };
Q_DECLARE_FLAGS(Foos, Foo)
Q_DECLARE_OPERATORS_FOR_FLAGS(Foos);
@ -497,7 +625,7 @@ void tst_QFlags::adl()
}
// (statically) check QTypeInfo for QFlags instantiations:
enum MyEnum { Zero, One, Two, Four=4 };
enum MyEnum : IntegerSize::Unsigned { Zero, One, Two, Four=4 };
Q_DECLARE_FLAGS( MyFlags, MyEnum )
Q_DECLARE_OPERATORS_FOR_FLAGS( MyFlags )

View File

@ -1858,6 +1858,14 @@ void tst_QMetaType::isEnum()
int type6 = ::qMetaTypeId<isEnumTest_Enum1>();
QVERIFY((QMetaType(type6).flags() & QMetaType::IsEnumeration) == QMetaType::IsEnumeration);
// QFlags are considered enums
QCOMPARE(QMetaType::fromType<QFlags<isEnumTest_Enum0>>().flags() & QMetaType::IsEnumeration,
QMetaType::IsEnumeration);
QCOMPARE(QMetaType::fromType<QFlags<isEnumTest_Enum1>>().flags() & QMetaType::IsEnumeration,
QMetaType::IsEnumeration);
QCOMPARE(QMetaType::fromType<QFlags<isEnumTest_Struct0::A>>().flags() & QMetaType::IsEnumeration,
QMetaType::IsEnumeration);
}
enum E1 : unsigned char {};
@ -1870,12 +1878,13 @@ namespace myflags {
enum Flag1 : int { A, B };
enum Flag2 : short { X, Y };
enum Flag3 : qlonglong { T, W = Q_INT64_C(0x1'0000'0002) };
Q_DECLARE_FLAGS(Flags1, myflags::Flag1);
Q_FLAG_NS(Flags1)
Q_DECLARE_FLAGS(Flags2, myflags::Flag2);
Q_FLAG_NS(Flags2)
Q_DECLARE_FLAGS(Flags3, myflags::Flag3)
}
template <typename T>
@ -1895,14 +1904,16 @@ void tst_QMetaType::underlyingType_data()
<< QMetaType::fromType<getUnderlyingTypeNormalized<isEnumTest_Enum1>>();
QTest::newRow("uchar") << QMetaType::fromType<E1>()
<< QMetaType::fromType<getUnderlyingTypeNormalized<E1>>();
QTest::newRow("long") << QMetaType::fromType<E2>()
<< QMetaType::fromType<getUnderlyingTypeNormalized<E2>>();
QTest::newRow("qlonglong") << QMetaType::fromType<E2>()
<< QMetaType::fromType<getUnderlyingTypeNormalized<E2>>();
QTest::newRow("class_ushort") << QMetaType::fromType<E3>()
<< QMetaType::fromType<getUnderlyingTypeNormalized<E3>>();
QTest::newRow("flags_int") << QMetaType::fromType<myflags::Flags1>()
<< QMetaType::fromType<int>();
QTest::newRow("flags_short") << QMetaType::fromType<myflags::Flags2>()
<< QMetaType::fromType<int>(); // sic, not short!
QTest::newRow("flags_qlonglong") << QMetaType::fromType<myflags::Flags3>()
<< QMetaType::fromType<qlonglong>();
}
void tst_QMetaType::underlyingType()