QSpan: ensure interoperability with std::span

We accepted QSpan as a NIH-type instead of waiting for C++20 and
std::span, because we said that there's no impedance mismatch between
the two, as they both implicitly convert into each other.

But we actually never checked that they do.

Fix this omission by adding constructors that treat std::span exactly
the same as QSpan itself, and adding the respective static_assert()s
to tst_QSpan to check that (within the constraints imposed by the
standard on std::span), they actually do convert into each other.

The only two problematic cases are that fixed-size std::span
constructors are explicit, so span is only constructible, not
convertible, from QSpan. Likewise, for an rvalue QSpan to be
acceptable to the std::span constructor, QSpan needs to opt-in to
enable_borrowed_range (while we're at it, do enable_view, too).

We so far have rejected adding these opt-ins for our own container
classes because we wanted to avoid the compile-time overhead of
including the huge <ranges> header into such central headers as those
that define our containers.

But std::span itself has to specialize these traits, and its range
contructor has to use them, so they must be available from <span>,
too, possibly the stdlib puts the definition into a much smaller
header. So just assume we can specialize it after including just
<span>, provided __cpp_lib_concepts is also defined.

Change-Id: I2202869b60c98047256b0fbcb12336f5d8e550ba
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
(cherry picked from commit 7f5b795f757ee62af71d8d47ccad19cbf681e0eb)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Marc Mutz 2023-12-06 22:02:42 +01:00 committed by Qt Cherry-pick Bot
parent 010ce12a82
commit 008072cffd
2 changed files with 80 additions and 0 deletions

View File

@ -37,6 +37,22 @@ namespace q20 {
template <typename T, std::size_t E = q20::dynamic_extent> class QSpan;
QT_BEGIN_INCLUDE_NAMESPACE
#ifdef __cpp_lib_span
#ifdef __cpp_lib_concepts
namespace std::ranges {
// Officially, these are defined in <ranges>, but that is a heavy-hitter header.
// OTOH, <span> must specialize these variable templates, too, so we assume that
// <span> includes some meaningful subset of <ranges> and just go ahead and use them:
template <typename T, std::size_t E>
constexpr inline bool enable_borrowed_range<QT_PREPEND_NAMESPACE(QSpan)<T, E>> = true;
template <typename T, std::size_t E>
constexpr inline bool enable_view<QT_PREPEND_NAMESPACE(QSpan)<T, E>> = true;
} // namespace std::ranges
#endif // __cpp_lib_concepts
#endif // __cpp_lib_span
QT_END_INCLUDE_NAMESPACE
namespace QSpanPrivate {
template <typename T, std::size_t E> class QSpanBase;
@ -50,6 +66,15 @@ struct is_qspan_helper<QSpanBase<T, E>> : std::true_type {};
template <typename T>
using is_qspan = is_qspan_helper<q20::remove_cvref_t<T>>;
template <typename T>
struct is_std_span_helper : std::false_type {};
#ifdef __cpp_lib_span
template <typename T, std::size_t E>
struct is_std_span_helper<std::span<T, E>> : std::true_type {};
#endif // __cpp_lib_span
template <typename T>
using is_std_span = is_std_span_helper<q20::remove_cvref_t<T>>;
template <typename T>
struct is_std_array_helper : std::false_type {};
template <typename T, std::size_t N>
@ -107,6 +132,7 @@ protected:
using is_compatible_range = std::conjunction<
// ### this needs more work, esp. extension to C++20 contiguous iterators
std::negation<is_qspan<Range>>,
std::negation<is_std_span<Range>>,
std::negation<is_std_array<Range>>,
std::negation<std::is_array<q20::remove_cvref_t<Range>>>,
is_compatible_range_helper<Range>
@ -197,6 +223,17 @@ public:
: QSpanBase(other.data(), other.size())
{}
#ifdef __cpp_lib_span
template <typename S, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::span<S, E> other) noexcept
: QSpanBase(other.data(), other.size())
{}
template <typename S, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::span<S> other)
: QSpanBase(other.data(), other.size())
{}
#endif // __cpp_lib_span
}; // class QSpanBase (fixed extent)
template <typename T>
@ -247,6 +284,13 @@ public:
Q_IMPLICIT constexpr QSpanBase(QSpan<S, N> other) noexcept
: QSpanBase(other.data(), other.size())
{}
#if __cpp_lib_span
template <typename S, size_t N, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::span<S, N> other) noexcept
: QSpanBase(other.data(), other.size())
{}
#endif // __cpp_lib_span
}; // class QSpanBase (dynamic extent)
} // namespace QSpanPrivate

View File

@ -8,6 +8,9 @@
#include <algorithm>
#include <array>
#ifdef __cpp_lib_span
#include <span>
#endif
#include <vector>
namespace {
@ -49,6 +52,17 @@ static_assert(std::is_trivially_destructible_v<QSpan<NotNothrowMovable, 0>>);
static_assert(std::is_convertible_v<QSpan<int, 42>, QSpan<int>>);
static_assert(std::is_convertible_v<QSpan<int, 0>, QSpan<int>>);
#ifdef __cpp_lib_span
static_assert(std::is_convertible_v<std::span<int, 42>, QSpan<int>>);
static_assert(std::is_convertible_v<std::span<int, 0>, QSpan<int>>);
#ifdef __cpp_lib_concepts
// requires enable_borrowed_range
static_assert(std::is_convertible_v<QSpan<int, 42>, std::span<int>>);
static_assert(std::is_convertible_v<QSpan<int, 0>, std::span<int>>);
#endif // __cpp_lib_concepts
#endif // __cpp_lib_span
//
// Mutable spans implicitly convert to read-only ones, but not vice versa:
//
@ -60,6 +74,28 @@ static_assert(!std::is_convertible_v<QSpan<const int>, QSpan<int>>);
static_assert(!std::is_convertible_v<QSpan<const int, 42>, QSpan<int, 42>>);
static_assert(!std::is_convertible_v<QSpan<const int, 0>, QSpan<int, 0>>);
#ifdef __cpp_lib_span
static_assert(std::is_convertible_v<std::span<int>, QSpan<const int>>);
static_assert(std::is_convertible_v<std::span<int, 42>, QSpan<const int, 42>>);
static_assert(std::is_convertible_v<std::span<int, 0>, QSpan<const int, 0>>);
static_assert(!std::is_convertible_v<std::span<const int>, QSpan<int>>);
static_assert(!std::is_convertible_v<std::span<const int, 42>, QSpan<int, 42>>);
static_assert(!std::is_convertible_v<std::span<const int, 0>, QSpan<int, 0>>);
static_assert(std::is_convertible_v<QSpan<int>, std::span<const int>>);
// fixed-size std::span constructors are explicit:
static_assert(!std::is_convertible_v<QSpan<int, 42>, std::span<const int, 42>>);
static_assert(!std::is_convertible_v<QSpan<int, 0>, std::span<const int, 0>>);
// observe: is_convertible<From,To>, but is_constuctible<To,From>!
static_assert(std::is_constructible_v<std::span<const int, 42>, QSpan<int, 42>>);
static_assert(std::is_constructible_v<std::span<const int, 0>, QSpan<int, 0>>);
static_assert(!std::is_convertible_v<QSpan<const int>, std::span<int>>);
static_assert(!std::is_convertible_v<QSpan<const int, 42>, std::span<int, 42>>);
static_assert(!std::is_convertible_v<QSpan<const int, 0>, std::span<int, 0>>);
#endif // __cpp_lib_span
class tst_QSpan : public QObject
{
Q_OBJECT