diff --git a/src/corelib/global/qflags.h b/src/corelib/global/qflags.h index c3174085537..dc187ef4f65 100644 --- a/src/corelib/global/qflags.h +++ b/src/corelib/global/qflags.h @@ -7,6 +7,7 @@ #include #include +#include #include QT_BEGIN_NAMESPACE @@ -51,38 +52,76 @@ constexpr inline QIncompatibleFlag::QIncompatibleFlag(int value) noexcept : i(va namespace QtPrivate { template struct IsQFlags : std::false_type {}; template struct IsQFlags> : std::true_type {}; + +template +class QFlagsStorage +{ + static_assert(sizeof(Enum) <= sizeof(quint64), + "Only enumerations 64 bits or smaller are supported."); + static_assert((std::is_enum::value), "QFlags is only usable on enumeration types."); + + static constexpr size_t IntegerSize = (std::max)(sizeof(Enum), sizeof(int)); + using Integers = QIntegerForSize; + +protected: + typedef typename std::conditional< + std::is_unsigned::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 )> +struct QFlagsStorageHelper : QFlagsStorage +{ + using QFlagsStorage::QFlagsStorage; +}; +template struct QFlagsStorageHelper : QFlagsStorage +{ + using QFlagsStorage::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(std::in_place, flag) {} +#ifdef QT_TYPESAFE_FLAGS + constexpr inline explicit operator QFlag() const noexcept { return QFlag(this->i); } +#endif +}; } // namespace QtPrivate template -class QFlags +class QFlags : public QtPrivate::QFlagsStorageHelper { - 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::value), "QFlags is only usable on enumeration types."); - + using Base = QtPrivate::QFlagsStorageHelper; 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::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 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 friend QDataStream &operator<<(QDataStream &, QFlags); template friend QDataStream &operator>>(QDataStream &, QFlags &); - Int i; + using Base::i; }; #ifndef Q_MOC_RUN diff --git a/src/corelib/global/qflags.qdoc b/src/corelib/global/qflags.qdoc index dd3b1e4c9b0..29d6784cae6 100644 --- a/src/corelib/global/qflags.qdoc +++ b/src/corelib/global/qflags.qdoc @@ -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. 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 QFlags::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 QFlags &QFlags::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 QFlags QFlags::operator&(uint mask) const \overload + \include qflags.qdoc unsafe-integer */ /*! diff --git a/src/corelib/io/qdebug.h b/src/corelib/io/qdebug.h index 36cc09edb2e..9b86c8eb1a1 100644 --- a/src/corelib/io/qdebug.h +++ b/src/corelib/io/qdebug.h @@ -579,7 +579,7 @@ inline QDebug operator<<(QDebug debug, Flags flags) // mutually exclusive. namespace QtPrivate { -template , bool SizedForQFlags = sizeof(T) <= 4> +template , bool = sizeof(T) <= sizeof(quint64)> struct EnumHasQFlag { static constexpr bool Value = false; }; template struct EnumHasQFlag : QtPrivate::IsQEnumHelper> {}; diff --git a/src/corelib/kernel/qmetatype.cpp b/src/corelib/kernel/qmetatype.cpp index 327c10ec3bb..13026548ef0 100644 --- a/src/corelib/kernel/qmetatype.cpp +++ b/src/corelib/kernel/qmetatype.cpp @@ -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()) { diff --git a/tests/auto/corelib/global/qflags/CMakeLists.txt b/tests/auto/corelib/global/qflags/CMakeLists.txt index 8b01ea774ff..7b0676ae9b4 100644 --- a/tests/auto/corelib/global/qflags/CMakeLists.txt +++ b/tests/auto/corelib/global/qflags/CMakeLists.txt @@ -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 diff --git a/tests/auto/corelib/global/qflags/tst_qflags.cpp b/tests/auto/corelib/global/qflags/tst_qflags.cpp index 287a7a0d41d..9b6cbeb69d1 100644 --- a/tests/auto/corelib/global/qflags/tst_qflags.cpp +++ b/tests/auto/corelib/global/qflags/tst_qflags.cpp @@ -14,10 +14,22 @@ #include +#if defined(__cpp_concepts) && __has_include() +# include +#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::isComplex ); static_assert( QTypeInfo::isRelocatable ); static_assert( !std::is_pointer_v ); +void tst_QFlags::size() +{ + static_assert(sizeof(UnsignedFlags) >= sizeof(IntegerSize::Unsigned)); + static_assert(sizeof(MyStrictFlags) == sizeof(IntegerSize::Unsigned)); +} + +template 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 void noCastToFromQFlag_template() +{ + // Verify that non-32-bit QFlags doesn't have QFlag support + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + static_assert(!std::is_constructible_v); + +#if defined(__cpp_concepts) && __has_include() + static_assert(!std::equality_comparable_with); + static_assert(!std::equality_comparable_with); +#endif +} + +void tst_QFlags::castToFromQFlag() +{ + if constexpr (sizeof(IntegerSize::Signed) == sizeof(int)) { + castToFromQFlag_template(); + } else { + noCastToFromQFlag_template(); + } +} + 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 ) diff --git a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp index 81bf5d5ea89..e7c45ac3dae 100644 --- a/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp +++ b/tests/auto/corelib/kernel/qmetatype/tst_qmetatype.cpp @@ -1858,6 +1858,14 @@ void tst_QMetaType::isEnum() int type6 = ::qMetaTypeId(); QVERIFY((QMetaType(type6).flags() & QMetaType::IsEnumeration) == QMetaType::IsEnumeration); + + // QFlags are considered enums + QCOMPARE(QMetaType::fromType>().flags() & QMetaType::IsEnumeration, + QMetaType::IsEnumeration); + QCOMPARE(QMetaType::fromType>().flags() & QMetaType::IsEnumeration, + QMetaType::IsEnumeration); + QCOMPARE(QMetaType::fromType>().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 @@ -1895,14 +1904,16 @@ void tst_QMetaType::underlyingType_data() << QMetaType::fromType>(); QTest::newRow("uchar") << QMetaType::fromType() << QMetaType::fromType>(); - QTest::newRow("long") << QMetaType::fromType() - << QMetaType::fromType>(); + QTest::newRow("qlonglong") << QMetaType::fromType() + << QMetaType::fromType>(); QTest::newRow("class_ushort") << QMetaType::fromType() << QMetaType::fromType>(); QTest::newRow("flags_int") << QMetaType::fromType() << QMetaType::fromType(); QTest::newRow("flags_short") << QMetaType::fromType() << QMetaType::fromType(); // sic, not short! + QTest::newRow("flags_qlonglong") << QMetaType::fromType() + << QMetaType::fromType(); } void tst_QMetaType::underlyingType()