QDebug: add streaming operators for std::tuple
Unlike the existing one for std::pair, make the operator SCARY, using the newly-added StreamTypeErased type-erasure (added in 5cdd1f594d26e1d4f84b00741be1ab7231458512). [ChangeLog][QtCore][QDebug] Can now stream std::tuple. Change-Id: I46670ac32eaee7f0ea5f4543ebcf59e19b394166 Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
This commit is contained in:
parent
2c8302682e
commit
12b41c3332
@ -1086,6 +1086,33 @@ QByteArray QDebug::toBytesImpl(StreamTypeErased s, const void *obj)
|
||||
return result;
|
||||
}
|
||||
|
||||
/*!
|
||||
\internal
|
||||
\since 6.9
|
||||
|
||||
Outputs a heterogeneous product type (pair, tuple, or anything that
|
||||
implements the Tuple Protocol). The class name is described by "\a ns
|
||||
\c{::} \a what", while the addresses of the \a n elements are stored in the
|
||||
array \a data. The formatters are stored in the array \a ops.
|
||||
|
||||
If \a ns is empty, only \a what is used.
|
||||
*/
|
||||
QDebug &QDebug::putTupleLikeImplImpl(const char *ns, const char *what,
|
||||
size_t n, StreamTypeErased *ops, const void **data)
|
||||
{
|
||||
const QDebugStateSaver saver(*this);
|
||||
nospace();
|
||||
if (ns && *ns)
|
||||
*this << ns << "::";
|
||||
*this << what << '(';
|
||||
while (n--) {
|
||||
(*ops++)(*this, *data++);
|
||||
if (n)
|
||||
*this << ", ";
|
||||
}
|
||||
return *this << ')';
|
||||
}
|
||||
|
||||
/*!
|
||||
\fn template <class T> QDebug operator<<(QDebug debug, const QList<T> &list)
|
||||
\relates QDebug
|
||||
@ -1179,6 +1206,14 @@ QByteArray QDebug::toBytesImpl(StreamTypeErased s, const void *obj)
|
||||
\c T need to support streaming into QDebug.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <class...Ts, QDebug::if_streamable<Ts...>> QDebug &QDebug::operator<<(const std::tuple<Ts...> &tuple)
|
||||
\since 6.9
|
||||
|
||||
Writes the contents of \a tuple to the stream. All \c Ts... need to support
|
||||
streaming into QDebug.
|
||||
*/
|
||||
|
||||
/*!
|
||||
\fn template <class T1, class T2> QDebug operator<<(QDebug debug, const std::pair<T1, T2> &pair)
|
||||
\relates QDebug
|
||||
|
@ -27,6 +27,8 @@
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <tuple>
|
||||
#include <QtCore/q20type_traits.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
@ -82,6 +84,11 @@ class QT6_ONLY(Q_CORE_EXPORT) QDebug : public QIODeviceBase
|
||||
QT7_ONLY(Q_CORE_EXPORT) void putUInt128(const void *i);
|
||||
QT7_ONLY(Q_CORE_EXPORT) void putQtOrdering(QtOrderingPrivate::QtOrderingTypeFlag flags,
|
||||
Qt::partial_ordering order);
|
||||
|
||||
template <typename...Ts>
|
||||
using if_streamable = std::enable_if_t<
|
||||
std::conjunction_v<QTypeTraits::has_ostream_operator<QDebug, Ts>...>
|
||||
, bool>;
|
||||
public:
|
||||
explicit QDebug(QIODevice *device) : stream(new Stream(device)) {}
|
||||
explicit QDebug(QString *string) : stream(new Stream(string)) {}
|
||||
@ -241,6 +248,32 @@ private:
|
||||
using StreamTypeErased = void(*)(QDebug&, const void*);
|
||||
QT7_ONLY(Q_CORE_EXPORT) static QString toStringImpl(StreamTypeErased s, const void *obj);
|
||||
QT7_ONLY(Q_CORE_EXPORT) static QByteArray toBytesImpl(StreamTypeErased s, const void *obj);
|
||||
QT7_ONLY(Q_CORE_EXPORT) QDebug &putTupleLikeImplImpl(const char *ns, const char *what, size_t n,
|
||||
StreamTypeErased *ops, const void **data);
|
||||
|
||||
template <typename TupleLike, size_t...Is>
|
||||
QDebug &putTupleLikeImpl(const char *ns, const char *what, const TupleLike &t,
|
||||
std::index_sequence<Is...>)
|
||||
{
|
||||
if constexpr (sizeof...(Is)) {
|
||||
StreamTypeErased ops[] = {
|
||||
&streamTypeErased<q20::remove_cvref_t<std::tuple_element_t<Is, TupleLike>>>...
|
||||
};
|
||||
const void *data[] = {
|
||||
std::addressof(std::get<Is>(t))...
|
||||
};
|
||||
return putTupleLikeImplImpl(ns, what, sizeof...(Is), ops, data);
|
||||
} else {
|
||||
return putTupleLikeImplImpl(ns, what, 0, nullptr, nullptr);
|
||||
}
|
||||
}
|
||||
|
||||
template <typename TupleLike>
|
||||
QDebug &putTupleLike(const char *ns, const char *what, const TupleLike &t)
|
||||
{
|
||||
using Indexes = std::make_index_sequence<std::tuple_size_v<TupleLike>>;
|
||||
return putTupleLikeImpl(ns, what, t, Indexes{});
|
||||
}
|
||||
public:
|
||||
template <typename T>
|
||||
static QString toString(const T &object)
|
||||
@ -254,6 +287,12 @@ public:
|
||||
return toBytesImpl(&streamTypeErased<T>, std::addressof(object));
|
||||
}
|
||||
|
||||
template <typename...Ts, if_streamable<Ts...> = true>
|
||||
QDebug &operator<<(const std::tuple<Ts...> &t)
|
||||
{
|
||||
return putTupleLike("std", "tuple", t);
|
||||
}
|
||||
|
||||
private:
|
||||
template <typename T>
|
||||
using if_ordering_type = std::enable_if_t<QtOrderingPrivate::is_ordering_type_v<T>, bool>;
|
||||
|
@ -25,6 +25,7 @@ namespace pmr = std::pmr;
|
||||
#else
|
||||
namespace pmr = std;
|
||||
#endif
|
||||
#include <tuple>
|
||||
|
||||
using namespace std::chrono;
|
||||
using namespace q20::chrono;
|
||||
@ -34,6 +35,7 @@ static_assert(QTypeTraits::has_ostream_operator_v<QDebug, int>);
|
||||
static_assert(QTypeTraits::has_ostream_operator_v<QDebug, QMetaType>);
|
||||
static_assert(QTypeTraits::has_ostream_operator_v<QDebug, QList<int>>);
|
||||
static_assert(QTypeTraits::has_ostream_operator_v<QDebug, QMap<int, QString>>);
|
||||
static_assert(QTypeTraits::has_ostream_operator_v<QDebug, std::tuple<int, QString, QMap<int, QString>>>);
|
||||
struct NonStreamable {};
|
||||
static_assert(!QTypeTraits::has_ostream_operator_v<QDebug, NonStreamable>);
|
||||
static_assert(!QTypeTraits::has_ostream_operator_v<QDebug, QList<NonStreamable>>);
|
||||
@ -81,6 +83,7 @@ private slots:
|
||||
void qDebugQUtf8StringView() const;
|
||||
void qDebugQLatin1String() const;
|
||||
void qDebugStdPair() const;
|
||||
void qDebugStdTuple() const;
|
||||
void qDebugStdString() const;
|
||||
void qDebugStdStringView() const;
|
||||
void qDebugStdWString() const;
|
||||
@ -724,6 +727,49 @@ void tst_QDebug::qDebugStdPair() const
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QDebug::qDebugStdTuple() const
|
||||
{
|
||||
QByteArray file, function;
|
||||
int line = 0;
|
||||
MessageHandlerSetter mhs(myMessageHandler);
|
||||
{
|
||||
QDebug d = qDebug();
|
||||
d << std::tuple(42, u"foo"_s, 4.2) << std::tuple(42);
|
||||
d.nospace().noquote() << std::tuple(u"baz"_s);
|
||||
}
|
||||
#ifndef QT_NO_MESSAGELOGCONTEXT
|
||||
file = __FILE__; line = __LINE__ - 5; function = Q_FUNC_INFO;
|
||||
#endif
|
||||
QCOMPARE(s_msgType, QtDebugMsg);
|
||||
QCOMPARE(s_msg, R"(std::tuple(42, "foo", 4.2) std::tuple(42) std::tuple(baz))"_L1);
|
||||
QCOMPARE(s_file, file);
|
||||
QCOMPARE(s_line, line);
|
||||
QCOMPARE(s_function, function);
|
||||
|
||||
/* simpler tests from now on */
|
||||
#ifndef Q_OS_QNX // QNX's stdlib doesn't appear to allow nesting tuples (at least not w/CTAD)...
|
||||
// nested:
|
||||
qDebug() << std::tuple(std::tuple(std::tuple(4.2, 42), ".42"), u"42"_s);
|
||||
QCOMPARE(s_msg, R"(std::tuple(std::tuple(std::tuple(4.2, 42), .42), "42"))"_L1);
|
||||
#endif
|
||||
// empty:
|
||||
qDebug() << std::tuple<>();
|
||||
QCOMPARE(s_msg, R"(std::tuple())"_L1);
|
||||
// very long:
|
||||
qDebug() << std::tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15,
|
||||
16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31);
|
||||
QCOMPARE(s_msg, R"(std::tuple(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31))"_L1);
|
||||
// with references:
|
||||
{
|
||||
auto d = 4.2; auto i = 42; auto s = u"foo"_s;
|
||||
qDebug() << std::tie(d, i, s);
|
||||
QCOMPARE(s_msg, R"(std::tuple(4.2, 42, "foo"))"_L1);
|
||||
s_msg.clear(); // avoid False Positives (next line outputs same as prior)
|
||||
qDebug() << std::forward_as_tuple(std::move(d), std::move(i), std::as_const(s));
|
||||
QCOMPARE(s_msg, R"(std::tuple(4.2, 42, "foo"))"_L1);
|
||||
}
|
||||
}
|
||||
|
||||
void tst_QDebug::qDebugStdString() const
|
||||
{
|
||||
QString file, function;
|
||||
|
Loading…
x
Reference in New Issue
Block a user