// Copyright (C) 2023 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #ifdef __cpp_lib_span #include #endif #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>); #ifdef __cpp_lib_span static_assert(std::is_convertible_v, QSpan>); static_assert(std::is_convertible_v, QSpan>); #ifdef __cpp_lib_concepts // requires enable_borrowed_range static_assert(std::is_convertible_v, std::span>); static_assert(std::is_convertible_v, std::span>); #endif // __cpp_lib_concepts #endif // __cpp_lib_span // // 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>); #ifdef __cpp_lib_span 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>); static_assert(std::is_convertible_v, std::span>); // fixed-size std::span constructors are explicit: static_assert(!std::is_convertible_v, std::span>); static_assert(!std::is_convertible_v, std::span>); // observe: is_convertible, but is_constuctible! static_assert(std::is_constructible_v, QSpan>); static_assert(std::is_constructible_v, QSpan>); static_assert(!std::is_convertible_v, std::span>); static_assert(!std::is_convertible_v, std::span>); static_assert(!std::is_convertible_v, std::span>); #endif // __cpp_lib_span // Spans don't convert from nonsense: static_assert(!std::is_constructible_v, int&&>); 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 fromStdInitializerList() 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_NE(s.cbegin(), s.cend()); QCOMPARE_NE(s.crbegin(), s.crend()); QCOMPARE_EQ(s.end() - s.begin(), s.size()); QCOMPARE_EQ(s.cend() - s.cbegin(), s.size()); QCOMPARE_EQ(s.rend() - s.rbegin(), s.size()); QCOMPARE_EQ(s.crend() - s.crbegin(), s.size()); QCOMPARE_EQ(std::addressof(s.front()), std::addressof(*s.begin())); QCOMPARE_EQ(std::addressof(s.front()), std::addressof(*s.cbegin())); 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.crbegin())); 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.cbegin(), s.cend()); QCOMPARE_EQ(s.rbegin(), s.rend()); QCOMPARE_EQ(s.crbegin(), s.crend()); } 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.cbegin(), 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::fromStdInitializerList() const { std::initializer_list il = {42, 84, 168, 336}; QSpan sci = il; // CTAD // special case: always deduced as : static_assert(std::is_same_v>); QCOMPARE_EQ(sci.size(), qsizetype(il.size())); QCOMPARE_EQ(sci.data(), il.begin()); check_nonempty_span(sci, 4); RETURN_IF_FAILED(); } 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"