QSpan: don't detach Qt containers

When converting an implicitly-shared Qt container to QSpan, the QSpan
ctor would call .data(), which detaches said Qt container (if it's not
const). This is what must happen for mutable spans (QSpan<int>), but
is not necessary for non-mutable spans (QSpan<const int>).

Fix by copying the potential const from QSpan::element_type to the
Range object in the resp. QSpan ctor. This makes a QSpan over a const
element_type mark the range const before passing it to adl_data() (=
member-data()). For a non-const element_type, nothing changes.

[ChangeLog][QtCore][QSpan] No longer detaches implicitly-shared Qt
containers converted to QSpan<const T, N>. Note that std::span<const
T, N> will, however, detach such containers, so we recommend to use
std::as_const() with implcitly-shared Qt containers, as always.

Fixes: QTBUG-132133
Pick-to: 6.9
Change-Id: I9fdae20994d2c900bc5b45b44db3901d10f8838a
Reviewed-by: Thiago Macieira <thiago.macieira@intel.com>
This commit is contained in:
Marc Mutz 2024-12-24 13:34:25 +01:00
parent 793309693a
commit 287234704b
3 changed files with 7 additions and 4 deletions

View File

@ -44,6 +44,10 @@ QT_END_INCLUDE_NAMESPACE
namespace QSpanPrivate {
template <typename From, typename To>
std::conditional_t<std::is_const_v<From>, const To &, To &> // like [forward]/6.1 COPY_CONST
const_propagated(To &in) { return in; }
template <typename T, std::size_t E> class QSpanBase;
template <typename T>
@ -208,7 +212,7 @@ public:
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)
: QSpanBase(QSpanPrivate::adl_data(QSpanPrivate::const_propagated<T>(r)), // no forward<>() here (std doesn't have it, either)
qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>()
{}
@ -280,7 +284,7 @@ public:
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)
: QSpanBase(QSpanPrivate::adl_data(QSpanPrivate::const_propagated<T>(r)), // no forward<>() here (std doesn't have it, either)
qsizetype(QSpanPrivate::adl_size(r))) // ditto, no forward<>()
{}

View File

@ -104,6 +104,7 @@
\list
\li QSpan is using the signed qsizetype as \c{size_type}
whereas \c{std::span} uses \c{size_t}.
\li (since Qt 6.9) \c{QSpan<const T>} doesn't detach Qt containers, \c{std::span} does.
\li All QSpan constructors are implicit;
many \c{std::span} ones are \c{explicit}.
\li QSpan can be constructed from rvalue owning containers, \c{std::span} can not.

View File

@ -478,7 +478,6 @@ void tst_QSpan::constQSpansDontDetachQtContainers() const
[[maybe_unused]] const QList copy = li;
QVERIFY(!li.isDetached());
[[maybe_unused]] QSpan<const int> cvspan = li; // should not detach (QTBUG-132133)
QEXPECT_FAIL("", "QTBUG-132133", Continue);
QVERIFY(!li.isDetached());
[[maybe_unused]] QSpan<int> mvspan = li; // this _has_ to detach, though
QVERIFY(li.isDetached());
@ -489,7 +488,6 @@ void tst_QSpan::constQSpansDontDetachQtContainers() const
[[maybe_unused]] const QList copy = li;
QVERIFY(!li.isDetached());
[[maybe_unused]] QSpan<const int, 4> cfspan = li; // should not detach (QTBUG-132133)
QEXPECT_FAIL("", "QTBUG-132133", Continue);
QVERIFY(!li.isDetached());
[[maybe_unused]] QSpan<int, 4> mfspan = li; // this _has_ to detach, though
QVERIFY(li.isDetached());