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:
parent
c5b083dd13
commit
bd45bd2769
@ -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
371
src/corelib/tools/qspan_p.h
Normal 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
|
@ -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)
|
||||
|
9
tests/auto/corelib/tools/qspan/CMakeLists.txt
Normal file
9
tests/auto/corelib/tools/qspan/CMakeLists.txt
Normal 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
|
||||
)
|
363
tests/auto/corelib/tools/qspan/tst_qspan.cpp
Normal file
363
tests/auto/corelib/tools/qspan/tst_qspan.cpp
Normal 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"
|
Loading…
x
Reference in New Issue
Block a user