Long live QSpan!

QSpan is Qt's version of std::span. While we usually try not to
reimplement std functionality anymore, the situation is different with
QSpan. Spans are non-owning containers, so the usual impedance
mismatch between owning STL and Qt containers doesn't apply here:
QSpan implicitly converts to std::span and vice versa, making STL and
Qt APIs using spans completely interoperable.

We add QSpan mainly for two reasons: First, we don't want to wait
until we require C++20 in Qt and can use std::span. Second, in the
view of this author, some design decisions in std::span hurt the
primary use-case of spans: type-erasure for containers. This results
in two major deviations of QSpan from std::span: First, any rvalue
container is convertible to QSpan, allowing seamless passing of owning
containers to functions taking spans:

    void sspan(std::span<T>);
    void qspan(QSpan<T>);

    std::vector<T> v();
    sspan(v()); // ERROR: rvalue owning container
    auto tmp = v();
    sspan(tmp); // OK, lvalue
    qspan(v()); // OK

This author believes that it's more helpful to have compilers and
static checkers warn about a particular wrong usage than to make
perfectly valid use-cases impossible or needlessly verbose to code.

The second deviation from std::span is that fixed-size span
constructors are also implicit. This isn't as clear-cut, because an
explicit QSpan{arg} isn't per-se bad. However, it means you can't
transparently change from a function taking decltype(arg) to one
taking QSpan and back. Since that's exactly what we intend to do in Qt
going forward, in the interest of source-compatibility, the ctors are
all implicit.

Otherwise, the API of QSpan follows the std::span API very
closely. Like std::span, QSpan isn't equality_comparable, because it's
not clear what equality means for spans (element-wise equal, or (ptr,
size)-wise equal?). The major API additions are Qt-ish versions of std
API functions: isEmpty() on top of empty() and sliced() instead of
subspan(). The (nullary) first()/last() functions (Qt speak for
front()/back()) clash with the std::span function templates of the
same name, so are not provided.

This patch adds QSpan as private API. We intend to make it public API
in the future.

Fixes: QTBUG-108124
Change-Id: I3f660be90eb408b9e66ff9eacf5da4cba17212a6
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
Reviewed-by: Qt CI Bot <qt_ci_bot@qt-project.org>
Reviewed-by: Dennis Oberst <dennis.oberst@qt.io>
Reviewed-by: Ivan Solovev <ivan.solovev@qt.io>
(cherry picked from commit f82cf6333e4e21c96d8b6bb272392f8142ead2b7)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Marc Mutz 2023-05-26 13:15:44 +02:00 committed by Qt Cherry-pick Bot
parent c5b083dd13
commit bd45bd2769
5 changed files with 745 additions and 0 deletions

View File

@ -304,6 +304,7 @@ qt_internal_add_module(Core
tools/qsharedpointer.cpp tools/qsharedpointer.h
tools/qsharedpointer_impl.h
tools/qsize.cpp tools/qsize.h
tools/qspan_p.h
tools/qstack.h
tools/qtaggedpointer.h
tools/qtools_p.h

371
src/corelib/tools/qspan_p.h Normal file
View File

@ -0,0 +1,371 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#ifndef QSPAN_P_H
#define QSPAN_P_H
//
// W A R N I N G
// -------------
//
// This file is not part of the Qt API. It exists purely as an
// implementation detail. This header file may change from version to
// version without notice, or even be removed.
//
// We mean it.
//
#include <QtCore/qcompilerdetection.h>
#include <QtCore/qtypes.h>
#include <array>
#include <cstddef>
#include <cassert>
#include <QtCore/q20iterator.h>
#include <QtCore/q20memory.h>
#ifdef __cpp_lib_span
#include <span>
#endif
#include <QtCore/q20type_traits.h>
QT_BEGIN_NAMESPACE
// like std::dynamic_extent
namespace q20 {
inline constexpr std::size_t dynamic_extent = -1;
} // namespace q20
template <typename T, std::size_t E = q20::dynamic_extent> class QSpan;
namespace QSpanPrivate {
template <typename T, std::size_t E> class QSpanBase;
template <typename T>
struct is_qspan_helper : std::false_type {};
template <typename T, std::size_t E>
struct is_qspan_helper<QSpan<T, E>> : std::true_type {};
template <typename T, std::size_t E>
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_array_helper : std::false_type {};
template <typename T, std::size_t N>
struct is_std_array_helper<std::array<T, N>> : std::true_type {};
template <typename T>
using is_std_array = is_std_array_helper<q20::remove_cvref_t<T>>;
template <typename From, typename To>
using is_qualification_conversion =
std::is_convertible<From(*)[], To(*)[]>; // https://eel.is/c++draft/span.cons#note-1
template <typename From, typename To>
constexpr inline bool is_qualification_conversion_v = is_qualification_conversion<From, To>::value;
// Replacements for std::ranges::XXX(), but only bringing in ADL XXX()s,
// not doing the extra work C++20 requires
template <typename Range>
decltype(auto) adl_begin(Range &&r) { using std::begin; return begin(r); }
template <typename Range>
decltype(auto) adl_data(Range &&r) { using std::data; return data(r); }
template <typename Range>
decltype(auto) adl_size(Range &&r) { using std::size; return size(r); }
// Replacement for std::ranges::iterator_t (which depends on C++20 std::ranges::begin)
// This one uses adl_begin() instead.
template <typename Range>
using iterator_t = decltype(QSpanPrivate::adl_begin(std::declval<Range&>()));
template <typename Range>
using range_reference_t = q20::iter_reference_t<QSpanPrivate::iterator_t<Range>>;
template <typename T>
class QSpanCommon {
protected:
template <typename Iterator>
using is_compatible_iterator = std::conjunction<
std::is_base_of<
std::random_access_iterator_tag,
typename std::iterator_traits<Iterator>::iterator_category
>,
is_qualification_conversion<
std::remove_reference_t<q20::iter_reference_t<Iterator>>,
T
>
>;
template <typename Iterator, typename End>
using is_compatible_iterator_and_sentinel = std::conjunction<
is_compatible_iterator<Iterator>,
std::negation<std::is_convertible<End, std::size_t>>
>;
template <typename Range, typename = void> // wrap use of SFINAE-unfriendly iterator_t:
struct is_compatible_range_helper : std::false_type {};
template <typename Range>
struct is_compatible_range_helper<Range, std::void_t<QSpanPrivate::iterator_t<Range>>>
: is_compatible_iterator<QSpanPrivate::iterator_t<Range>> {};
template <typename Range>
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_array<Range>>,
std::negation<std::is_array<q20::remove_cvref_t<Range>>>,
is_compatible_range_helper<Range>
>;
// constraints
template <typename Iterator>
using if_compatible_iterator = std::enable_if_t<
is_compatible_iterator<Iterator>::value
, bool>;
template <typename Iterator, typename End>
using if_compatible_iterator_and_sentinel = std::enable_if_t<
is_compatible_iterator_and_sentinel<Iterator, End>::value
, bool>;
template <typename Range>
using if_compatible_range = std::enable_if_t<is_compatible_range<Range>::value, bool>;
}; // class QSpanCommon
template <typename T, std::size_t E>
class QSpanBase : protected QSpanCommon<T>
{
static_assert(E < size_t{(std::numeric_limits<qsizetype>::max)()},
"QSpan only supports extents that fit into the signed size type (qsizetype).");
struct Enabled_t { explicit Enabled_t() = default; };
static inline constexpr Enabled_t Enable{};
template <typename S, std::size_t N>
using if_compatible_array = std::enable_if_t<
N == E && is_qualification_conversion_v<S, T>
, bool>;
template <typename S>
using if_qualification_conversion = std::enable_if_t<
is_qualification_conversion_v<S, T>
, bool>;
protected:
using Base = QSpanCommon<T>;
// data members:
T *m_data;
static constexpr qsizetype m_size = qsizetype(E);
// types and constants:
static constexpr size_t extent = E;
// constructors (need to be public d/t the way ctor inheriting works):
public:
template <std::size_t E2 = E, std::enable_if_t<E2 == 0, bool> = true>
Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr} {}
template <typename It, typename Base::template if_compatible_iterator<It> = true>
explicit constexpr QSpanBase(It first, qsizetype count)
: m_data{q20::to_address(first)}
{
Q_ASSERT(count == m_size);
}
template <typename It, typename End, typename Base::template if_compatible_iterator_and_sentinel<It, End> = true>
explicit constexpr QSpanBase(It first, End last)
: QSpanBase(first, last - first) {}
template <size_t N, std::enable_if_t<N == E, bool> = true>
Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t<T> (&arr)[N]) noexcept
: QSpanBase(arr, N) {}
template <typename S, size_t N, if_compatible_array<S, N> = true>
Q_IMPLICIT constexpr QSpanBase(std::array<S, N> &arr) noexcept
: QSpanBase(arr.data(), N) {}
template <typename S, size_t N, if_compatible_array<S, N> = true>
Q_IMPLICIT constexpr QSpanBase(const std::array<S, N> &arr) noexcept
: QSpanBase(arr.data(), N) {}
template <typename Range, typename Base::template if_compatible_range<Range> = true>
Q_IMPLICIT constexpr QSpanBase(Range &&r)
: QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either)
qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>()
{}
template <typename S, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(QSpan<S, E> other) noexcept
: QSpanBase(other.data(), other.size())
{}
template <typename S, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(QSpan<S> other)
: QSpanBase(other.data(), other.size())
{}
}; // class QSpanBase (fixed extent)
template <typename T>
class QSpanBase<T, q20::dynamic_extent> : protected QSpanCommon<T>
{
template <typename S>
using if_qualification_conversion = std::enable_if_t<
is_qualification_conversion_v<S, T>
, bool>;
protected:
using Base = QSpanCommon<T>;
// data members:
T *m_data;
qsizetype m_size;
// constructors (need to be public d/t the way ctor inheriting works):
public:
Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr}, m_size{0} {}
template <typename It, typename Base::template if_compatible_iterator<It> = true>
Q_IMPLICIT constexpr QSpanBase(It first, qsizetype count)
: m_data{q20::to_address(first)}, m_size{count} {}
template <typename It, typename End, typename Base::template if_compatible_iterator_and_sentinel<It, End> = true>
Q_IMPLICIT constexpr QSpanBase(It first, End last)
: QSpanBase(first, last - first) {}
template <size_t N>
Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t<T> (&arr)[N]) noexcept
: QSpanBase(arr, N) {}
template <typename S, size_t N, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::array<S, N> &arr) noexcept
: QSpanBase(arr.data(), N) {}
template <typename S, size_t N, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(const std::array<S, N> &arr) noexcept
: QSpanBase(arr.data(), N) {}
template <typename Range, typename Base::template if_compatible_range<Range> = true>
Q_IMPLICIT constexpr QSpanBase(Range &&r)
: QSpanBase(QSpanPrivate::adl_data(r), // no forward<>() here (std doesn't have it, either)
qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>()
{}
template <typename S, size_t N, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(QSpan<S, N> other) noexcept
: QSpanBase(other.data(), other.size())
{}
}; // class QSpanBase (dynamic extent)
} // namespace QSpanPrivate
template <typename T, std::size_t E>
class QSpan : private QSpanPrivate::QSpanBase<T, E>
{
using Base = QSpanPrivate::QSpanBase<T, E>;
Q_ALWAYS_INLINE constexpr void verify([[maybe_unused]] qsizetype pos = 0,
[[maybe_unused]] qsizetype n = 1) const
{
Q_ASSERT(pos >= 0);
Q_ASSERT(pos <= size());
Q_ASSERT(n >= 0);
Q_ASSERT(n <= size() - pos);
}
template <std::size_t N>
static constexpr bool subspan_always_succeeds_v = N <= E && E != q20::dynamic_extent;
public:
// constants and types
using element_type = T;
using value_type = std::remove_cv_t<element_type>;
using size_type = qsizetype; // difference to std::span
using difference_type = qptrdiff; // difference to std::span
using pointer = element_type*;
using const_pointer = const element_type*;
using reference = element_type&;
using const_reference = const element_type&;
using iterator = pointer; // implementation-defined choice
using reverse_iterator = std::reverse_iterator<iterator>;
static constexpr size_type extent = E;
// [span.cons], constructors, copy, and assignment
using Base::Base;
// [span.obs]
[[nodiscard]] constexpr size_type size() const noexcept { return this->m_size; }
[[nodiscard]] constexpr size_type size_bytes() const noexcept { return size() * sizeof(T); }
[[nodiscard]] constexpr bool empty() const noexcept { return size() == 0; }
// [span.elem]
[[nodiscard]] constexpr reference operator[](size_type idx) const
{ verify(idx); return data()[idx]; }
[[nodiscard]] constexpr reference front() const { verify(); return *data(); }
[[nodiscard]] constexpr reference back() const { verify(); return data()[size() - 1]; }
[[nodiscard]] constexpr pointer data() const noexcept { return this->m_data; }
// [span.iterators]
[[nodiscard]] constexpr auto begin() const noexcept { return data(); }
[[nodiscard]] constexpr auto end() const noexcept { return data() + size(); }
[[nodiscard]] constexpr auto rbegin() const noexcept { return reverse_iterator{end()}; }
[[nodiscard]] constexpr auto rend() const noexcept { return reverse_iterator{begin()}; }
// [span.sub]
template <std::size_t Count>
[[nodiscard]] constexpr QSpan<T, Count> first() const
noexcept(subspan_always_succeeds_v<Count>)
{
static_assert(Count <= E,
"Count cannot be larger than the span's extent.");
verify(0, Count);
return QSpan<T, Count>{data(), Count};
}
template <std::size_t Count>
[[nodiscard]] constexpr QSpan<T, Count> last() const
noexcept(subspan_always_succeeds_v<Count>)
{
static_assert(Count <= E,
"Count cannot be larger than the span's extent.");
verify(0, Count);
return QSpan<T, Count>{data() + (size() - Count), Count};
}
template <std::size_t Offset>
[[nodiscard]] constexpr auto subspan() const
noexcept(subspan_always_succeeds_v<Offset>)
{
static_assert(Offset <= E,
"Offset cannot be larger than the span's extent.");
verify(Offset, 0);
if constexpr (E == q20::dynamic_extent)
return QSpan<T>{data() + Offset, qsizetype(size() - Offset)};
else
return QSpan<T, E - Offset>{data() + Offset, qsizetype(E - Offset)};
}
template <std::size_t Offset, std::size_t Count>
[[nodiscard]] constexpr auto subspan() const
noexcept(subspan_always_succeeds_v<Offset + Count>)
{ return subspan<Offset>().template first<Count>(); }
[[nodiscard]] constexpr QSpan<T> first(size_type n) const { verify(0, n); return {data(), n}; }
[[nodiscard]] constexpr QSpan<T> last(size_type n) const { verify(0, n); return {data() + (size() - n), n}; }
[[nodiscard]] constexpr QSpan<T> subspan(size_type pos) const { verify(pos, 0); return {data() + pos, size() - pos}; }
[[nodiscard]] constexpr QSpan<T> subspan(size_type pos, size_type n) const { return subspan(pos).first(n); }
// Qt-compatibility API:
[[nodiscard]] bool isEmpty() const noexcept { return empty(); }
// nullary first()/last() clash with first<>() and last<>(), so they're not provided for QSpan
[[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); }
}; // class QSpan
// [span.deduct]
template <class It, class EndOrSize>
QSpan(It, EndOrSize) -> QSpan<std::remove_reference_t<q20::iter_reference_t<It>>>;
template <class T, std::size_t N>
QSpan(T (&)[N]) -> QSpan<T, N>;
template <class T, std::size_t N>
QSpan(std::array<T, N> &) -> QSpan<T, N>;
template <class T, std::size_t N>
QSpan(const std::array<T, N> &) -> QSpan<const T, N>;
template <class R>
QSpan(R&&) -> QSpan<std::remove_reference_t<QSpanPrivate::range_reference_t<R>>>;
QT_END_NAMESPACE
#endif // QSPAN_P_H

View File

@ -46,6 +46,7 @@ add_subdirectory(qset)
add_subdirectory(qsharedpointer)
add_subdirectory(qsize)
add_subdirectory(qsizef)
add_subdirectory(qspan)
add_subdirectory(qstl)
add_subdirectory(qvarlengtharray)
add_subdirectory(qversionnumber)

View File

@ -0,0 +1,9 @@
# Copyright (C) 2023 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_test(tst_qspan
SOURCES
tst_qspan.cpp
LIBRARIES
Qt::CorePrivate
)

View File

@ -0,0 +1,363 @@
// Copyright (C) 2023 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include <private/qspan_p.h>
#include <QList>
#include <QTest>
#include <algorithm>
#include <array>
#include <vector>
namespace {
struct NotNothrowMovable {
NotNothrowMovable(NotNothrowMovable &&) noexcept(false) {};
NotNothrowMovable &operator=(NotNothrowMovable &&) noexcept(false) { return *this; };
};
static_assert(!std::is_nothrow_move_constructible_v<NotNothrowMovable>);
static_assert(!std::is_nothrow_move_assignable_v<NotNothrowMovable>);
} // unnamed namespace
//
// QSpan is nothrow movable even if the payload type is not:
//
static_assert(std::is_nothrow_move_constructible_v<QSpan<NotNothrowMovable>>);
static_assert(std::is_nothrow_move_constructible_v<QSpan<NotNothrowMovable, 42>>);
static_assert(std::is_nothrow_move_constructible_v<QSpan<NotNothrowMovable, 0>>);
static_assert(std::is_nothrow_move_assignable_v<QSpan<NotNothrowMovable>>);
static_assert(std::is_nothrow_move_assignable_v<QSpan<NotNothrowMovable, 42>>);
static_assert(std::is_nothrow_move_assignable_v<QSpan<NotNothrowMovable, 0>>);
//
// All QSpans are trivially destructible and trivially copyable:
//
static_assert(std::is_trivially_copyable_v<QSpan<NotNothrowMovable>>);
static_assert(std::is_trivially_copyable_v<QSpan<NotNothrowMovable, 42>>);
static_assert(std::is_trivially_copyable_v<QSpan<NotNothrowMovable, 0>>);
static_assert(std::is_trivially_destructible_v<QSpan<NotNothrowMovable>>);
static_assert(std::is_trivially_destructible_v<QSpan<NotNothrowMovable, 42>>);
static_assert(std::is_trivially_destructible_v<QSpan<NotNothrowMovable, 0>>);
//
// Fixed-size QSpans implicitly convert to variable-sized ones:
//
static_assert(std::is_convertible_v<QSpan<int, 42>, QSpan<int>>);
static_assert(std::is_convertible_v<QSpan<int, 0>, QSpan<int>>);
//
// Mutable spans implicitly convert to read-only ones, but not vice versa:
//
static_assert(std::is_convertible_v<QSpan<int>, QSpan<const int>>);
static_assert(std::is_convertible_v<QSpan<int, 42>, QSpan<const int, 42>>);
static_assert(std::is_convertible_v<QSpan<int, 0>, QSpan<const int, 0>>);
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>>);
class tst_QSpan : public QObject
{
Q_OBJECT
public:
using QObject::QObject;
private Q_SLOTS:
void onlyZeroExtentSpansHaveDefaultCtors() const;
void zeroExtentSpansMaintainADataPointer() const;
void fromArray() const;
void fromStdArray() const;
void fromZeroSizeStdArray() const;
void fromStdVector() const;
void fromQList() const;
private:
template <typename T, std::size_t N>
void check_nonempty_span(QSpan<T, N>, qsizetype expectedSize) const;
template <typename T, std::size_t N>
void check_empty_span_incl_subspans(QSpan<T, N>) const;
template <typename T, std::size_t N>
void check_empty_span(QSpan<T, N>) const;
template <typename T, std::size_t N>
void check_null_span(QSpan<T, N>) const;
template <std::size_t ExpectedExtent, typename C>
void from_container_impl(C &&c) const;
template <typename C>
void from_variable_size_container_impl(C &&c) const;
};
#define RETURN_IF_FAILED() \
do { if (QTest::currentTestFailed()) return; } while (false)
void tst_QSpan::onlyZeroExtentSpansHaveDefaultCtors() const
{
static_assert(std::is_nothrow_default_constructible_v<QSpan<int, 0>>);
static_assert(std::is_nothrow_default_constructible_v<QSpan<const int, 0>>);
static_assert(std::is_nothrow_default_constructible_v<QSpan<int>>);
static_assert(std::is_nothrow_default_constructible_v<QSpan<const int, 0>>);
QSpan<int, 0> si;
check_null_span(si);
RETURN_IF_FAILED();
QSpan<const int, 0> sci;
check_null_span(sci);
RETURN_IF_FAILED();
QSpan<int> sdi;
check_null_span(sdi);
RETURN_IF_FAILED();
QSpan<const int> sdci;
check_null_span(sdci);
RETURN_IF_FAILED();
static_assert(!std::is_default_constructible_v<QSpan<int, 1>>);
static_assert(!std::is_default_constructible_v<QSpan<const int, 42>>);
}
void tst_QSpan::zeroExtentSpansMaintainADataPointer() const
{
int i;
QSpan<int, 0> si{&i, 0};
QCOMPARE(si.data(), &i);
check_empty_span_incl_subspans(si);
RETURN_IF_FAILED();
QSpan<const int, 0> sci{&i, 0};
QCOMPARE(sci.data(), &i);
check_empty_span_incl_subspans(sci);
RETURN_IF_FAILED();
QSpan<int, 0> sdi{&i, 0};
QCOMPARE(sdi.data(), &i);
check_empty_span_incl_subspans(sdi);
RETURN_IF_FAILED();
QSpan<const int, 0> sdci{&i, 0};
QCOMPARE(sdci.data(), &i);
check_empty_span_incl_subspans(sdci);
RETURN_IF_FAILED();
}
template <typename T, std::size_t N>
void tst_QSpan::check_nonempty_span(QSpan<T, N> s, qsizetype expectedSize) const
{
static_assert(N > 0);
QCOMPARE_GT(expectedSize, 0); // otherwise, use check_empty_span!
QVERIFY(!s.empty());
QVERIFY(!s.isEmpty());
QCOMPARE_EQ(s.size(), expectedSize);
QCOMPARE_NE(s.data(), nullptr);
QCOMPARE_NE(s.begin(), s.end());
QCOMPARE_NE(s.rbegin(), s.rend());
QCOMPARE_EQ(s.end() - s.begin(), s.size());
QCOMPARE_EQ(s.rend() - s.rbegin(), s.size());
QCOMPARE_EQ(std::addressof(s.front()), std::addressof(*s.begin()));
QCOMPARE_EQ(std::addressof(s.front()), std::addressof(s[0]));
QCOMPARE_EQ(std::addressof(s.back()), std::addressof(*s.rbegin()));
QCOMPARE_EQ(std::addressof(s.back()), std::addressof(s[s.size() - 1]));
// ### more?
if (expectedSize == 1) {
// don't run into Mandates: Offset >= Extent
if constexpr (N > 0) { // incl. N == std::dynamic_extent
check_empty_span_incl_subspans(s.template subspan<1>());
RETURN_IF_FAILED();
}
check_empty_span_incl_subspans(s.subspan(1));
RETURN_IF_FAILED();
} else {
// don't run into Mandates: Offset >= Extent
if constexpr (N > 1) { // incl. N == std::dynamic_extent
check_nonempty_span(s.template subspan<1>(), expectedSize - 1);
RETURN_IF_FAILED();
}
check_nonempty_span(s.subspan(1), expectedSize - 1);
RETURN_IF_FAILED();
}
}
template <typename T, std::size_t N>
void tst_QSpan::check_empty_span(QSpan<T, N> s) const
{
QVERIFY(s.empty());
QVERIFY(s.isEmpty());
QCOMPARE_EQ(s.size(), 0);
QCOMPARE_EQ(s.begin(), s.end());
QCOMPARE_EQ(s.rbegin(), s.rend());
}
template <typename T, std::size_t N>
void tst_QSpan::check_empty_span_incl_subspans(QSpan<T, N> s) const
{
check_empty_span(s);
RETURN_IF_FAILED();
{
const auto fi = s.template first<0>();
check_empty_span(fi);
RETURN_IF_FAILED();
QCOMPARE_EQ(fi.data(), s.data());
}
{
const auto la = s.template last<0>();
check_empty_span(la);
RETURN_IF_FAILED();
QCOMPARE_EQ(la.data(), s.data());
}
{
const auto ss = s.template subspan<0>();
check_empty_span(ss);
RETURN_IF_FAILED();
QCOMPARE_EQ(ss.data(), s.data());
}
{
const auto ss = s.template subspan<0, 0>();
check_empty_span(ss);
RETURN_IF_FAILED();
QCOMPARE_EQ(ss.data(), s.data());
}
{
const auto fi = s.first(0);
check_empty_span(fi);
RETURN_IF_FAILED();
QCOMPARE_EQ(fi.data(), s.data());
}
{
const auto la = s.last(0);
check_empty_span(la);
RETURN_IF_FAILED();
QCOMPARE_EQ(la.data(), s.data());
}
{
const auto ss = s.subspan(0);
check_empty_span(ss);
RETURN_IF_FAILED();
QCOMPARE_EQ(ss.data(), s.data());
}
{
const auto ss = s.subspan(0, 0);
check_empty_span(ss);
RETURN_IF_FAILED();
QCOMPARE_EQ(ss.data(), s.data());
}
}
template<typename T, std::size_t N>
void tst_QSpan::check_null_span(QSpan<T, N> s) const
{
QCOMPARE_EQ(s.data(), nullptr);
QCOMPARE_EQ(s.begin(), nullptr);
QCOMPARE_EQ(s.end(), nullptr);
check_empty_span_incl_subspans(s);
}
template <std::size_t ExpectedExtent, typename C>
void tst_QSpan::from_container_impl(C &&c) const
{
const auto c_size = qsizetype(QSpanPrivate::adl_size(c));
const auto c_data = QSpanPrivate::adl_data(c);
{
QSpan si = c; // CTAD
static_assert(std::is_same_v<decltype(si), QSpan<int, ExpectedExtent>>);
QCOMPARE_EQ(si.size(), c_size);
QCOMPARE_EQ(si.data(), c_data);
check_nonempty_span(si, c_size);
RETURN_IF_FAILED();
QSpan<const int> sci = c;
QCOMPARE_EQ(sci.size(), c_size);
QCOMPARE_EQ(sci.data(), c_data);
check_nonempty_span(sci, c_size);
RETURN_IF_FAILED();
}
{
QSpan sci = std::as_const(c); // CTAD
static_assert(std::is_same_v<decltype(sci), QSpan<const int, ExpectedExtent>>);
QCOMPARE_EQ(sci.size(), c_size);
QCOMPARE_EQ(sci.data(), c_data);
check_nonempty_span(sci, c_size);
RETURN_IF_FAILED();
}
}
template <typename C>
void tst_QSpan::from_variable_size_container_impl(C &&c) const
{
constexpr auto E = q20::dynamic_extent;
from_container_impl<E>(std::forward<C>(c));
}
void tst_QSpan::fromArray() const
{
int ai[] = {42, 84, 168, 336};
from_container_impl<4>(ai);
}
void tst_QSpan::fromStdArray() const
{
std::array<int, 4> ai = {42, 84, 168, 336};
from_container_impl<4>(ai);
}
void tst_QSpan::fromZeroSizeStdArray() const
{
std::array<int, 0> ai = {};
QSpan si = ai; // CTAD
static_assert(std::is_same_v<decltype(si), QSpan<int, 0>>);
QCOMPARE_EQ(si.data(), ai.data());
const std::array<int, 0> cai = {};
QSpan csi = cai; // CTAD
static_assert(std::is_same_v<decltype(csi), QSpan<const int, 0>>);
QCOMPARE_EQ(csi.data(), cai.data());
std::array<const int, 0> aci = {};
QSpan sci = aci; // CTAD
static_assert(std::is_same_v<decltype(sci), QSpan<const int, 0>>);
QCOMPARE_EQ(sci.data(), aci.data());
std::array<const int, 0> caci = {};
QSpan csci = caci; // CTAD
static_assert(std::is_same_v<decltype(csci), QSpan<const int, 0>>);
QCOMPARE_EQ(csci.data(), caci.data());
}
void tst_QSpan::fromStdVector() const
{
std::vector<int> vi = {42, 84, 168, 336};
from_variable_size_container_impl(vi);
}
void tst_QSpan::fromQList() const
{
QList<int> li = {42, 84, 168, 336};
from_variable_size_container_impl(li);
}
#undef RETURN_IF_FAILED
QTEST_APPLESS_MAIN(tst_QSpan);
#include "tst_qspan.moc"