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:
parent
010ce12a82
commit
008072cffd
@ -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
|
||||
|
@ -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
|
||||
|
Loading…
x
Reference in New Issue
Block a user