diff options
author | Ivan Solovev <ivan.solovev@qt.io> | 2022-09-30 13:12:42 +0200 |
---|---|---|
committer | Ivan Solovev <ivan.solovev@qt.io> | 2022-10-06 14:26:24 +0200 |
commit | dff985140a9f31632ba6630ec3391be55051a7ac (patch) | |
tree | 380babdac720c2cec4d10f493edf95581c4791d3 | |
parent | 394e9a8d06e475a46d6e14cc96dfd12d431cb412 (diff) |
Move qAsConst() and qExchange() to QtTypeTraits
It's the least worst place we could think of.
Add the qttypetraits.h include to qforeach, because otherwise
the tests fail to build.
Task-number: QTBUG-106154
Task-number: QTBUG-99313
Change-Id: I841f22e887f351146589377ec2376957e2adfd5e
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
-rw-r--r-- | src/corelib/global/qforeach.h | 1 | ||||
-rw-r--r-- | src/corelib/global/qglobal.cpp | 96 | ||||
-rw-r--r-- | src/corelib/global/qglobal.h | 18 | ||||
-rw-r--r-- | src/corelib/global/qttypetraits.h | 19 | ||||
-rw-r--r-- | src/corelib/global/qttypetraits.qdoc | 96 |
5 files changed, 116 insertions, 114 deletions
diff --git a/src/corelib/global/qforeach.h b/src/corelib/global/qforeach.h index 80d0c41aa2..e8396c4fb0 100644 --- a/src/corelib/global/qforeach.h +++ b/src/corelib/global/qforeach.h @@ -7,6 +7,7 @@ #include <QtCore/qglobal.h> #include <QtCore/qtdeprecationmarkers.h> +#include <QtCore/qttypetraits.h> QT_BEGIN_NAMESPACE diff --git a/src/corelib/global/qglobal.cpp b/src/corelib/global/qglobal.cpp index 7077efb662..e444755f1b 100644 --- a/src/corelib/global/qglobal.cpp +++ b/src/corelib/global/qglobal.cpp @@ -228,102 +228,6 @@ void qAbort() // strftime() -- not used (except in tests) /*! - \fn template <typename T> typename std::add_const<T>::type &qAsConst(T &t) - \relates <QtGlobal> - \since 5.7 - - Returns \a t cast to \c{const T}. - - This function is a Qt implementation of C++17's std::as_const(), - a cast function like std::move(). But while std::move() turns - lvalues into rvalues, this function turns non-const lvalues into - const lvalues. Like std::as_const(), it doesn't work on rvalues, - because it cannot be efficiently implemented for rvalues without - leaving dangling references. - - Its main use in Qt is to prevent implicitly-shared Qt containers - from detaching: - \snippet code/src_corelib_global_qglobal.cpp as-const-0 - - Of course, in this case, you could (and probably should) have declared - \c s as \c const in the first place: - \snippet code/src_corelib_global_qglobal.cpp as-const-1 - but often that is not easily possible. - - It is important to note that qAsConst() does not copy its argument, - it just performs a \c{const_cast<const T&>(t)}. This is also the reason - why it is designed to fail for rvalues: The returned reference would go - stale too soon. So while this works (but detaches the returned object): - \snippet code/src_corelib_global_qglobal.cpp as-const-2 - - this would not: - \snippet code/src_corelib_global_qglobal.cpp as-const-3 - - To prevent this construct from compiling (and failing at runtime), qAsConst() has - a second, deleted, overload which binds to rvalues. -*/ - -/*! - \fn template <typename T> void qAsConst(const T &&t) - \relates <QtGlobal> - \since 5.7 - \overload - - This overload is deleted to prevent a dangling reference in code like - \snippet code/src_corelib_global_qglobal.cpp as-const-4 -*/ - -/*! - \fn template <typename T, typename U = T> T qExchange(T &obj, U &&newValue) - \relates <QtGlobal> - \since 5.14 - - Replaces the value of \a obj with \a newValue and returns the old value of \a obj. - - This is Qt's implementation of std::exchange(). It differs from std::exchange() - only in that it is \c constexpr already in C++14, and available on all supported - compilers. - - Here is how to use qExchange() to implement move constructors: - \code - MyClass(MyClass &&other) - : m_pointer{qExchange(other.m_pointer, nullptr)}, - m_int{qExchange(other.m_int, 0)}, - m_vector{std::move(other.m_vector)}, - ... - \endcode - - For members of class type, we can use std::move(), as their move-constructor will - do the right thing. But for scalar types such as raw pointers or integer type, move - is the same as copy, which, particularly for pointers, is not what we expect. So, we - cannot use std::move() for such types, but we can use std::exchange()/qExchange() to - make sure the source object's member is already reset by the time we get to the - initialization of our next data member, which might come in handy if the constructor - exits with an exception. - - Here is how to use qExchange() to write a loop that consumes the collection it - iterates over: - \code - for (auto &e : qExchange(collection, {}) - doSomethingWith(e); - \endcode - - Which is equivalent to the following, much more verbose code: - \code - { - auto tmp = std::move(collection); - collection = {}; // or collection.clear() - for (auto &e : tmp) - doSomethingWith(e); - } // destroys 'tmp' - \endcode - - This is perfectly safe, as the for-loop keeps the result of qExchange() alive for as - long as the loop runs, saving the declaration of a temporary variable. Be aware, though, - that qExchange() returns a non-const object, so Qt containers may detach. -*/ - -/*! \macro Q_UNUSED(name) \relates <QtGlobal> diff --git a/src/corelib/global/qglobal.h b/src/corelib/global/qglobal.h index 4e9e0609f1..ef42bc9d35 100644 --- a/src/corelib/global/qglobal.h +++ b/src/corelib/global/qglobal.h @@ -54,24 +54,6 @@ QT_BEGIN_NAMESPACE # define Q_UNIMPLEMENTED() qWarning("Unimplemented code.") #endif - -// this adds const to non-const objects (like std::as_const) -template <typename T> -constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; } -// prevent rvalue arguments: -template <typename T> -void qAsConst(const T &&) = delete; - -// like std::exchange -template <typename T, typename U = T> -constexpr T qExchange(T &t, U &&newValue) -noexcept(std::conjunction_v<std::is_nothrow_move_constructible<T>, std::is_nothrow_assignable<T &, U>>) -{ - T old = std::move(t); - t = std::forward<U>(newValue); - return old; -} - QT_END_NAMESPACE // We need to keep QTypeInfo, QSysInfo, QFlags, qDebug & family in qglobal.h for compatibility with Qt 4. diff --git a/src/corelib/global/qttypetraits.h b/src/corelib/global/qttypetraits.h index 02921f2b64..1832a1a734 100644 --- a/src/corelib/global/qttypetraits.h +++ b/src/corelib/global/qttypetraits.h @@ -7,6 +7,7 @@ #include <QtCore/qtconfigmacros.h> #include <type_traits> +#include <utility> #if 0 #pragma qt_class(QtTypeTraits) @@ -22,6 +23,24 @@ constexpr std::underlying_type_t<Enum> qToUnderlying(Enum e) noexcept return static_cast<std::underlying_type_t<Enum>>(e); } +// this adds const to non-const objects (like std::as_const) +template <typename T> +constexpr typename std::add_const<T>::type &qAsConst(T &t) noexcept { return t; } +// prevent rvalue arguments: +template <typename T> +void qAsConst(const T &&) = delete; + +// like std::exchange +template <typename T, typename U = T> +constexpr T qExchange(T &t, U &&newValue) +noexcept(std::conjunction_v<std::is_nothrow_move_constructible<T>, + std::is_nothrow_assignable<T &, U>>) +{ + T old = std::move(t); + t = std::forward<U>(newValue); + return old; +} + QT_END_NAMESPACE #endif // QTTYPETRAITS_H diff --git a/src/corelib/global/qttypetraits.qdoc b/src/corelib/global/qttypetraits.qdoc index 5c87f81a8b..6bb1ceba2c 100644 --- a/src/corelib/global/qttypetraits.qdoc +++ b/src/corelib/global/qttypetraits.qdoc @@ -9,3 +9,99 @@ Converts the enumerator \a e to the equivalent value expressed in its enumeration's underlying type. */ + +/*! + \fn template <typename T> typename std::add_const<T>::type &qAsConst(T &t) + \relates <QtTypeTraits> + \since 5.7 + + Returns \a t cast to \c{const T}. + + This function is a Qt implementation of C++17's std::as_const(), + a cast function like std::move(). But while std::move() turns + lvalues into rvalues, this function turns non-const lvalues into + const lvalues. Like std::as_const(), it doesn't work on rvalues, + because it cannot be efficiently implemented for rvalues without + leaving dangling references. + + Its main use in Qt is to prevent implicitly-shared Qt containers + from detaching: + \snippet code/src_corelib_global_qglobal.cpp as-const-0 + + Of course, in this case, you could (and probably should) have declared + \c s as \c const in the first place: + \snippet code/src_corelib_global_qglobal.cpp as-const-1 + but often that is not easily possible. + + It is important to note that qAsConst() does not copy its argument, + it just performs a \c{const_cast<const T&>(t)}. This is also the reason + why it is designed to fail for rvalues: The returned reference would go + stale too soon. So while this works (but detaches the returned object): + \snippet code/src_corelib_global_qglobal.cpp as-const-2 + + this would not: + \snippet code/src_corelib_global_qglobal.cpp as-const-3 + + To prevent this construct from compiling (and failing at runtime), qAsConst() has + a second, deleted, overload which binds to rvalues. +*/ + +/*! + \fn template <typename T> void qAsConst(const T &&t) + \relates <QtTypeTraits> + \since 5.7 + \overload + + This overload is deleted to prevent a dangling reference in code like + \snippet code/src_corelib_global_qglobal.cpp as-const-4 +*/ + +/*! + \fn template <typename T, typename U = T> T qExchange(T &obj, U &&newValue) + \relates <QtTypeTraits> + \since 5.14 + + Replaces the value of \a obj with \a newValue and returns the old value of \a obj. + + This is Qt's implementation of std::exchange(). It differs from std::exchange() + only in that it is \c constexpr already in C++14, and available on all supported + compilers. + + Here is how to use qExchange() to implement move constructors: + \code + MyClass(MyClass &&other) + : m_pointer{qExchange(other.m_pointer, nullptr)}, + m_int{qExchange(other.m_int, 0)}, + m_vector{std::move(other.m_vector)}, + ... + \endcode + + For members of class type, we can use std::move(), as their move-constructor will + do the right thing. But for scalar types such as raw pointers or integer type, move + is the same as copy, which, particularly for pointers, is not what we expect. So, we + cannot use std::move() for such types, but we can use std::exchange()/qExchange() to + make sure the source object's member is already reset by the time we get to the + initialization of our next data member, which might come in handy if the constructor + exits with an exception. + + Here is how to use qExchange() to write a loop that consumes the collection it + iterates over: + \code + for (auto &e : qExchange(collection, {}) + doSomethingWith(e); + \endcode + + Which is equivalent to the following, much more verbose code: + \code + { + auto tmp = std::move(collection); + collection = {}; // or collection.clear() + for (auto &e : tmp) + doSomethingWith(e); + } // destroys 'tmp' + \endcode + + This is perfectly safe, as the for-loop keeps the result of qExchange() alive for as + long as the loop runs, saving the declaration of a temporary variable. Be aware, though, + that qExchange() returns a non-const object, so Qt containers may detach. +*/ |