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>
This commit is contained in:
parent
394e9a8d06
commit
dff985140a
@ -7,6 +7,7 @@
|
|||||||
|
|
||||||
#include <QtCore/qglobal.h>
|
#include <QtCore/qglobal.h>
|
||||||
#include <QtCore/qtdeprecationmarkers.h>
|
#include <QtCore/qtdeprecationmarkers.h>
|
||||||
|
#include <QtCore/qttypetraits.h>
|
||||||
|
|
||||||
QT_BEGIN_NAMESPACE
|
QT_BEGIN_NAMESPACE
|
||||||
|
|
||||||
|
@ -227,102 +227,6 @@ void qAbort()
|
|||||||
// localtime() -- but not localtime_r(), which we use when threaded
|
// localtime() -- but not localtime_r(), which we use when threaded
|
||||||
// strftime() -- not used (except in tests)
|
// 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)
|
\macro Q_UNUSED(name)
|
||||||
\relates <QtGlobal>
|
\relates <QtGlobal>
|
||||||
|
@ -54,24 +54,6 @@ QT_BEGIN_NAMESPACE
|
|||||||
# define Q_UNIMPLEMENTED() qWarning("Unimplemented code.")
|
# define Q_UNIMPLEMENTED() qWarning("Unimplemented code.")
|
||||||
#endif
|
#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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
// We need to keep QTypeInfo, QSysInfo, QFlags, qDebug & family in qglobal.h for compatibility with Qt 4.
|
// We need to keep QTypeInfo, QSysInfo, QFlags, qDebug & family in qglobal.h for compatibility with Qt 4.
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include <QtCore/qtconfigmacros.h>
|
#include <QtCore/qtconfigmacros.h>
|
||||||
|
|
||||||
#include <type_traits>
|
#include <type_traits>
|
||||||
|
#include <utility>
|
||||||
|
|
||||||
#if 0
|
#if 0
|
||||||
#pragma qt_class(QtTypeTraits)
|
#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);
|
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
|
QT_END_NAMESPACE
|
||||||
|
|
||||||
#endif // QTTYPETRAITS_H
|
#endif // QTTYPETRAITS_H
|
||||||
|
@ -9,3 +9,99 @@
|
|||||||
Converts the enumerator \a e to the equivalent value expressed in its
|
Converts the enumerator \a e to the equivalent value expressed in its
|
||||||
enumeration's underlying type.
|
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.
|
||||||
|
*/
|
||||||
|
Loading…
Reference in New Issue
Block a user