QSpan: add as_(writable_)bytes

For std::span, these are free functions in namespace std and therefore
work for anything implicitly convertible to std::span,
incl. QSpan. But they're C++20, QSpan needs something for C++17
builds.

By adding them as hidden friends for QSpan, we allow unqualifid calls
to transparently resolve to the respective overload, ensuring
source-compatibility between std:: and QSpan, and, eventually, a
transition back to std::as_*_bytes.

I considered the alternative to add these functions in the q20
namespace, but q20::as_bytes() would have to take QSpan, and QSpan is
convertible from more types than std::span, so we wouldn't be able to
guarantee that std::as_bytes(t) works for all T t for which
q20::as_bytes(t) works, the fundamental guarantee for namespace qNN.

[ChangeLog][QtCore][QSpan] Added std::span-style as_bytes() and
as_writable_bytes() functions.

Fixes: QTBUG-125489
Change-Id: Ia9a7560c7843e182892608178433be7349c825ba
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2024-05-21 07:34:21 +02:00
parent 71bc951870
commit 462c958a92
3 changed files with 88 additions and 0 deletions

View File

@ -433,6 +433,29 @@ public:
[[nodiscard]] constexpr QSpan<T> sliced(size_type pos) const { return subspan(pos); }
[[nodiscard]] constexpr QSpan<T> sliced(size_type pos, size_type n) const { return subspan(pos, n); }
private:
// [span.objectrep]
[[nodiscard]] friend
QSpan<const std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>
as_bytes(QSpan s) noexcept
{
using R = QSpan<const std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>;
return R{reinterpret_cast<const std::byte *>(s.data()), s.size_bytes()};
}
template <typename U>
using if_mutable = std::enable_if_t<!std::is_const_v<U>, bool>;
#ifndef Q_QDOC
template <typename T2 = T, if_mutable<T2> = true>
#endif
[[nodiscard]] friend
QSpan<std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>
as_writable_bytes(QSpan s) noexcept
{
using R = QSpan<std::byte, E == q20::dynamic_extent ? q20::dynamic_extent : E * sizeof(T)>;
return R{reinterpret_cast<std::byte *>(s.data()), s.size_bytes()};
}
}; // class QSpan
// [span.deduct]

View File

@ -647,3 +647,37 @@
\sa subspan(), first(QSpan<T,E>::size_type), last(QSpan<T,E>::size_type)
*/
/*!
\fn template <typename T, size_t E> auto QSpan<T, E>::as_bytes(QSpan s)
\since 6.8
Returns \a s as a \c{QSpan<const std::byte, E'>} whose size() is equal to
\c{s.size_bytes()}.
If \c{E} is \c{std::dynamic_extent} then so is \c{E'}.
Otherwise, \c{E' = E * sizeof(T)}.
\note \c{q20::dynamic_extent} is a C++17 backport of C++20's
\l{https://en.cppreference.com/w/cpp/container/span/dynamic_extent}{\c{std::dynamic_extent}}.
\sa as_writable_bytes(), size_bytes()
*/
/*!
\fn template <typename T, size_t E> auto QSpan<T, E>::as_writable_bytes(QSpan s)
\since 6.8
Returns \a s as a \c{QSpan<std::byte, E'>} whose size() is equal to
\c{s.size_bytes()}.
If \c{E} is \c{std::dynamic_extent} then so is \c{E'}.
Otherwise, \c{E' = E * sizeof(T)}.
\note This function participates in overload resolution only if
\c{!std::is_const_v<T>}.
\note \c{q20::dynamic_extent} is a C++17 backport of C++20's
\l{https://en.cppreference.com/w/cpp/container/span/dynamic_extent}{\c{std::dynamic_extent}}.
\sa as_bytes(), size_bytes()
*/

View File

@ -141,6 +141,9 @@ private:
void from_variable_size_container_impl(C &&c) const;
};
template <typename T>
const void *as_const_void(T *p) noexcept { return static_cast<const void *>(p); }
#define RETURN_IF_FAILED() \
do { if (QTest::currentTestFailed()) return; } while (false)
@ -334,6 +337,8 @@ void tst_QSpan::from_container_impl(C &&c) const
const auto c_data = QSpanPrivate::adl_data(c);
using V = std::remove_reference_t<QSpanPrivate::range_reference_t<C>>;
constexpr auto ExpectedBytesExtent
= ExpectedExtent == q20::dynamic_extent ? q20::dynamic_extent : ExpectedExtent * sizeof(V);
{
QSpan si = c; // CTAD
static_assert(std::is_same_v<decltype(si), QSpan<V, ExpectedExtent>>);
@ -344,6 +349,20 @@ void tst_QSpan::from_container_impl(C &&c) const
check_nonempty_span(si, c_size);
RETURN_IF_FAILED();
auto bi = as_bytes(si);
static_assert(std::is_same_v<decltype(bi), QSpan<const std::byte, ExpectedBytesExtent>>);
QCOMPARE_EQ(bi.size(), si.size_bytes());
QCOMPARE_EQ(as_const_void(bi.data()),
as_const_void(si.data()));
if constexpr (!std::is_const_v<V>) { // e.g. std::initializer_list<int>
auto wbi = as_writable_bytes(si);
static_assert(std::is_same_v<decltype(wbi), QSpan<std::byte, ExpectedBytesExtent>>);
QCOMPARE_EQ(wbi.size(), si.size_bytes());
QCOMPARE_EQ(as_const_void(wbi.data()),
as_const_void(si.data()));
}
QSpan<const int> sci = c;
QCOMPARE_EQ(sci.size(), c_size);
@ -351,6 +370,12 @@ void tst_QSpan::from_container_impl(C &&c) const
check_nonempty_span(sci, c_size);
RETURN_IF_FAILED();
auto bci = as_bytes(sci);
static_assert(std::is_same_v<decltype(bci), QSpan<const std::byte>>);
QCOMPARE_EQ(bci.size(), sci.size_bytes());
QCOMPARE_EQ(as_const_void(bci.data()),
as_const_void(sci.data()));
}
{
QSpan sci = std::as_const(c); // CTAD
@ -361,6 +386,12 @@ void tst_QSpan::from_container_impl(C &&c) const
check_nonempty_span(sci, c_size);
RETURN_IF_FAILED();
auto bci = as_bytes(sci);
static_assert(std::is_same_v<decltype(bci), QSpan<const std::byte, ExpectedBytesExtent>>);
QCOMPARE_EQ(bci.size(), sci.size_bytes());
QCOMPARE_EQ(as_const_void(bci.data()),
as_const_void(sci.data()));
}
}