diff --git a/src/corelib/CMakeLists.txt b/src/corelib/CMakeLists.txt index f1face02945..fb077806328 100644 --- a/src/corelib/CMakeLists.txt +++ b/src/corelib/CMakeLists.txt @@ -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 diff --git a/src/corelib/tools/qspan_p.h b/src/corelib/tools/qspan_p.h new file mode 100644 index 00000000000..c2fc6386938 --- /dev/null +++ b/src/corelib/tools/qspan_p.h @@ -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 +#include + +#include +#include +#include +#include +#include +#ifdef __cpp_lib_span +#include +#endif +#include + +QT_BEGIN_NAMESPACE + +// like std::dynamic_extent +namespace q20 { + inline constexpr std::size_t dynamic_extent = -1; +} // namespace q20 + +template class QSpan; + +namespace QSpanPrivate { + +template class QSpanBase; + +template +struct is_qspan_helper : std::false_type {}; +template +struct is_qspan_helper> : std::true_type {}; +template +struct is_qspan_helper> : std::true_type {}; +template +using is_qspan = is_qspan_helper>; + +template +struct is_std_array_helper : std::false_type {}; +template +struct is_std_array_helper> : std::true_type {}; +template +using is_std_array = is_std_array_helper>; + +template +using is_qualification_conversion = + std::is_convertible; // https://eel.is/c++draft/span.cons#note-1 +template +constexpr inline bool is_qualification_conversion_v = is_qualification_conversion::value; + +// Replacements for std::ranges::XXX(), but only bringing in ADL XXX()s, +// not doing the extra work C++20 requires +template +decltype(auto) adl_begin(Range &&r) { using std::begin; return begin(r); } +template +decltype(auto) adl_data(Range &&r) { using std::data; return data(r); } +template +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 +using iterator_t = decltype(QSpanPrivate::adl_begin(std::declval())); +template +using range_reference_t = q20::iter_reference_t>; + +template +class QSpanCommon { +protected: + template + using is_compatible_iterator = std::conjunction< + std::is_base_of< + std::random_access_iterator_tag, + typename std::iterator_traits::iterator_category + >, + is_qualification_conversion< + std::remove_reference_t>, + T + > + >; + template + using is_compatible_iterator_and_sentinel = std::conjunction< + is_compatible_iterator, + std::negation> + >; + template // wrap use of SFINAE-unfriendly iterator_t: + struct is_compatible_range_helper : std::false_type {}; + template + struct is_compatible_range_helper>> + : is_compatible_iterator> {}; + template + using is_compatible_range = std::conjunction< + // ### this needs more work, esp. extension to C++20 contiguous iterators + std::negation>, + std::negation>, + std::negation>>, + is_compatible_range_helper + >; + + // constraints + template + using if_compatible_iterator = std::enable_if_t< + is_compatible_iterator::value + , bool>; + template + using if_compatible_iterator_and_sentinel = std::enable_if_t< + is_compatible_iterator_and_sentinel::value + , bool>; + template + using if_compatible_range = std::enable_if_t::value, bool>; +}; // class QSpanCommon + +template +class QSpanBase : protected QSpanCommon +{ + static_assert(E < size_t{(std::numeric_limits::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 + using if_compatible_array = std::enable_if_t< + N == E && is_qualification_conversion_v + , bool>; + + template + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v + , bool>; +protected: + using Base = QSpanCommon; + + // 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 = true> + Q_IMPLICIT constexpr QSpanBase() noexcept : m_data{nullptr} {} + + template = true> + explicit constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)} + { + Q_ASSERT(count == m_size); + } + + template = true> + explicit constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(const std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = 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 = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) noexcept + : QSpanBase(other.data(), other.size()) + {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) + : QSpanBase(other.data(), other.size()) + {} + +}; // class QSpanBase (fixed extent) + +template +class QSpanBase : protected QSpanCommon +{ + template + using if_qualification_conversion = std::enable_if_t< + is_qualification_conversion_v + , bool>; +protected: + using Base = QSpanCommon; + + // 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 = true> + Q_IMPLICIT constexpr QSpanBase(It first, qsizetype count) + : m_data{q20::to_address(first)}, m_size{count} {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(It first, End last) + : QSpanBase(first, last - first) {} + + template + Q_IMPLICIT constexpr QSpanBase(q20::type_identity_t (&arr)[N]) noexcept + : QSpanBase(arr, N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = true> + Q_IMPLICIT constexpr QSpanBase(const std::array &arr) noexcept + : QSpanBase(arr.data(), N) {} + + template = 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 = true> + Q_IMPLICIT constexpr QSpanBase(QSpan other) noexcept + : QSpanBase(other.data(), other.size()) + {} +}; // class QSpanBase (dynamic extent) + +} // namespace QSpanPrivate + +template +class QSpan : private QSpanPrivate::QSpanBase +{ + using Base = QSpanPrivate::QSpanBase; + 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 + 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; + 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; + 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 + [[nodiscard]] constexpr QSpan first() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan{data(), Count}; + } + + template + [[nodiscard]] constexpr QSpan last() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Count <= E, + "Count cannot be larger than the span's extent."); + verify(0, Count); + return QSpan{data() + (size() - Count), Count}; + } + + template + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v) + { + static_assert(Offset <= E, + "Offset cannot be larger than the span's extent."); + verify(Offset, 0); + if constexpr (E == q20::dynamic_extent) + return QSpan{data() + Offset, qsizetype(size() - Offset)}; + else + return QSpan{data() + Offset, qsizetype(E - Offset)}; + } + + template + [[nodiscard]] constexpr auto subspan() const + noexcept(subspan_always_succeeds_v) + { return subspan().template first(); } + + [[nodiscard]] constexpr QSpan first(size_type n) const { verify(0, n); return {data(), n}; } + [[nodiscard]] constexpr QSpan last(size_type n) const { verify(0, n); return {data() + (size() - n), n}; } + [[nodiscard]] constexpr QSpan subspan(size_type pos) const { verify(pos, 0); return {data() + pos, size() - pos}; } + [[nodiscard]] constexpr QSpan 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 sliced(size_type pos) const { return subspan(pos); } + [[nodiscard]] constexpr QSpan sliced(size_type pos, size_type n) const { return subspan(pos, n); } + +}; // class QSpan + +// [span.deduct] +template +QSpan(It, EndOrSize) -> QSpan>>; +template +QSpan(T (&)[N]) -> QSpan; +template +QSpan(std::array &) -> QSpan; +template +QSpan(const std::array &) -> QSpan; +template +QSpan(R&&) -> QSpan>>; + +QT_END_NAMESPACE + +#endif // QSPAN_P_H diff --git a/tests/auto/corelib/tools/CMakeLists.txt b/tests/auto/corelib/tools/CMakeLists.txt index 9bcdc12bfc7..8eec56758b5 100644 --- a/tests/auto/corelib/tools/CMakeLists.txt +++ b/tests/auto/corelib/tools/CMakeLists.txt @@ -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) diff --git a/tests/auto/corelib/tools/qspan/CMakeLists.txt b/tests/auto/corelib/tools/qspan/CMakeLists.txt new file mode 100644 index 00000000000..f29179efe4b --- /dev/null +++ b/tests/auto/corelib/tools/qspan/CMakeLists.txt @@ -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 +) diff --git a/tests/auto/corelib/tools/qspan/tst_qspan.cpp b/tests/auto/corelib/tools/qspan/tst_qspan.cpp new file mode 100644 index 00000000000..aa47c7a01e5 --- /dev/null +++ b/tests/auto/corelib/tools/qspan/tst_qspan.cpp @@ -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 + +#include +#include + +#include +#include +#include + +namespace { + +struct NotNothrowMovable { + NotNothrowMovable(NotNothrowMovable &&) noexcept(false) {}; + NotNothrowMovable &operator=(NotNothrowMovable &&) noexcept(false) { return *this; }; +}; +static_assert(!std::is_nothrow_move_constructible_v); +static_assert(!std::is_nothrow_move_assignable_v); + +} // unnamed namespace + +// +// QSpan is nothrow movable even if the payload type is not: +// +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); +static_assert(std::is_nothrow_move_constructible_v>); + +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); +static_assert(std::is_nothrow_move_assignable_v>); + +// +// All QSpans are trivially destructible and trivially copyable: +// +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_trivially_copyable_v>); +static_assert(std::is_trivially_copyable_v>); + +static_assert(std::is_trivially_destructible_v>); +static_assert(std::is_trivially_destructible_v>); +static_assert(std::is_trivially_destructible_v>); + +// +// Fixed-size QSpans implicitly convert to variable-sized ones: +// +static_assert(std::is_convertible_v, QSpan>); +static_assert(std::is_convertible_v, QSpan>); + +// +// Mutable spans implicitly convert to read-only ones, but not vice versa: +// +static_assert(std::is_convertible_v, QSpan>); +static_assert(std::is_convertible_v, QSpan>); +static_assert(std::is_convertible_v, QSpan>); + +static_assert(!std::is_convertible_v, QSpan>); +static_assert(!std::is_convertible_v, QSpan>); +static_assert(!std::is_convertible_v, QSpan>); + +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 + void check_nonempty_span(QSpan, qsizetype expectedSize) const; + template + void check_empty_span_incl_subspans(QSpan) const; + template + void check_empty_span(QSpan) const; + template + void check_null_span(QSpan) const; + + template + void from_container_impl(C &&c) const; + template + 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>); + static_assert(std::is_nothrow_default_constructible_v>); + static_assert(std::is_nothrow_default_constructible_v>); + static_assert(std::is_nothrow_default_constructible_v>); + + QSpan si; + check_null_span(si); + RETURN_IF_FAILED(); + + QSpan sci; + check_null_span(sci); + RETURN_IF_FAILED(); + + QSpan sdi; + check_null_span(sdi); + RETURN_IF_FAILED(); + + QSpan sdci; + check_null_span(sdci); + RETURN_IF_FAILED(); + + static_assert(!std::is_default_constructible_v>); + static_assert(!std::is_default_constructible_v>); +} + +void tst_QSpan::zeroExtentSpansMaintainADataPointer() const +{ + int i; + QSpan si{&i, 0}; + QCOMPARE(si.data(), &i); + check_empty_span_incl_subspans(si); + RETURN_IF_FAILED(); + + QSpan sci{&i, 0}; + QCOMPARE(sci.data(), &i); + check_empty_span_incl_subspans(sci); + RETURN_IF_FAILED(); + + QSpan sdi{&i, 0}; + QCOMPARE(sdi.data(), &i); + check_empty_span_incl_subspans(sdi); + RETURN_IF_FAILED(); + + QSpan sdci{&i, 0}; + QCOMPARE(sdci.data(), &i); + check_empty_span_incl_subspans(sdci); + RETURN_IF_FAILED(); +} + +template +void tst_QSpan::check_nonempty_span(QSpan 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 +void tst_QSpan::check_empty_span(QSpan 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 +void tst_QSpan::check_empty_span_incl_subspans(QSpan 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 +void tst_QSpan::check_null_span(QSpan s) const +{ + QCOMPARE_EQ(s.data(), nullptr); + QCOMPARE_EQ(s.begin(), nullptr); + QCOMPARE_EQ(s.end(), nullptr); + check_empty_span_incl_subspans(s); +} + +template +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>); + + QCOMPARE_EQ(si.size(), c_size); + QCOMPARE_EQ(si.data(), c_data); + + check_nonempty_span(si, c_size); + RETURN_IF_FAILED(); + + QSpan 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>); + + QCOMPARE_EQ(sci.size(), c_size); + QCOMPARE_EQ(sci.data(), c_data); + + check_nonempty_span(sci, c_size); + RETURN_IF_FAILED(); + } +} + +template +void tst_QSpan::from_variable_size_container_impl(C &&c) const +{ + constexpr auto E = q20::dynamic_extent; + from_container_impl(std::forward(c)); +} + +void tst_QSpan::fromArray() const +{ + int ai[] = {42, 84, 168, 336}; + from_container_impl<4>(ai); +} + +void tst_QSpan::fromStdArray() const +{ + std::array ai = {42, 84, 168, 336}; + from_container_impl<4>(ai); +} + +void tst_QSpan::fromZeroSizeStdArray() const +{ + std::array ai = {}; + QSpan si = ai; // CTAD + static_assert(std::is_same_v>); + QCOMPARE_EQ(si.data(), ai.data()); + + const std::array cai = {}; + QSpan csi = cai; // CTAD + static_assert(std::is_same_v>); + QCOMPARE_EQ(csi.data(), cai.data()); + + std::array aci = {}; + QSpan sci = aci; // CTAD + static_assert(std::is_same_v>); + QCOMPARE_EQ(sci.data(), aci.data()); + + std::array caci = {}; + QSpan csci = caci; // CTAD + static_assert(std::is_same_v>); + QCOMPARE_EQ(csci.data(), caci.data()); +} + +void tst_QSpan::fromStdVector() const +{ + std::vector vi = {42, 84, 168, 336}; + from_variable_size_container_impl(vi); +} + +void tst_QSpan::fromQList() const +{ + QList li = {42, 84, 168, 336}; + from_variable_size_container_impl(li); +} + +#undef RETURN_IF_FAILED + +QTEST_APPLESS_MAIN(tst_QSpan); +#include "tst_qspan.moc"