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:
Marc Mutz 2024-10-21 14:56:01 +02:00
parent 2c8302682e
commit 12b41c3332
3 changed files with 120 additions and 0 deletions

View File

@ -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

View File

@ -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>;

View File

@ -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;