From c80f4750552818bf334cf23abf572c98dee675e0 Mon Sep 17 00:00:00 2001 From: Giuseppe D'Angelo Date: Mon, 4 Mar 2024 15:33:09 +0100 Subject: [PATCH] QSpan: add construction from initializer_list P2447 has been merged in C++26, backport the same functionality. This makes QSpan a proper replacement for a const QList& parameter, because now both can be built via a braced-init-list. // void f(const QList &l); // old void f(QSpan); // new f({1, 2, 3}); // now OK This is, technically speaking, SiC: in the presence of both `f` overloads, the code above would have called the QList one. Now instead the call is ambiguous. We've been there already -- this is QString and QStringView all over again, and the solution is the same: get rid of the owning container overload. I'd rather have this construction *sooner* rather than *later* in order to minimize the fallout. And just like QString vs QStringView, there's nothing really doable to prevent instant-dangling situations: QStringView v = getString(); // dangles QSpan s = {1, 2, 3}; // ditto except for using QSpan (QStringView) as a *parameter type only*. Note that QSpan with dynamic extent was already convertible from std::initializer_list through its ranged constructor. However this fact alone doesn't unlock the above syntax. QSpan with a static extent was also convertible for the same reason. (This is non-standard: std::span's range constructor for static extents is explicit, but QSpan doesn't follow that design choice and makes the constructors implicit instead.) Found in API-review. Change-Id: I160ab5b292b0c2568cd9a7ad1b4430085f475c29 Reviewed-by: Marc Mutz (cherry picked from commit 7f7b5ff3a1b617a3a1add1b1b6ad0718f0dcf143) Reviewed-by: Qt Cherry-pick Bot --- src/corelib/tools/qspan.h | 12 ++++++++++ src/corelib/tools/qspan.qdoc | 12 ++++++++++ tests/auto/corelib/tools/qspan/tst_qspan.cpp | 25 +++++++++++++++++++- 3 files changed, 48 insertions(+), 1 deletion(-) diff --git a/src/corelib/tools/qspan.h b/src/corelib/tools/qspan.h index e8aa8f3254f..c9de1005a77 100644 --- a/src/corelib/tools/qspan.h +++ b/src/corelib/tools/qspan.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #include #ifdef __cpp_lib_span @@ -224,6 +225,11 @@ public: : QSpanBase(other.data(), other.size()) {} + template , bool> = true> + Q_IMPLICIT constexpr QSpanBase(std::initializer_list> il) + : QSpanBase(il.begin(), il.size()) + {} + #ifdef __cpp_lib_span template = true> Q_IMPLICIT constexpr QSpanBase(std::span other) noexcept @@ -286,6 +292,11 @@ public: : QSpanBase(other.data(), other.size()) {} + template , bool> = true> + Q_IMPLICIT constexpr QSpanBase(std::initializer_list> il) noexcept + : QSpanBase(il.begin(), il.size()) + {} + #if __cpp_lib_span template = true> Q_IMPLICIT constexpr QSpanBase(std::span other) noexcept @@ -347,6 +358,7 @@ public: template = true> constexpr QSpan(Range &&r); template = true> constexpr QSpan(QSpan other) noexcept; template = true> constexpr QSpan(std::span other) noexcept; + constexpr QSpan(std::initializer_list il); #endif // Q_QDOC // [span.obs] diff --git a/src/corelib/tools/qspan.qdoc b/src/corelib/tools/qspan.qdoc index 474d5e8debf..472f122877d 100644 --- a/src/corelib/tools/qspan.qdoc +++ b/src/corelib/tools/qspan.qdoc @@ -341,6 +341,18 @@ \endlist */ +/*! + \fn template QSpan::QSpan(std::initializer_list il); + + Constructs a QSpan referencing the data in the supplied initializer list \a il. + + \note This constructor participates in overload resolution only if \c{T} is \c{const}-qualified. + + \note This constructor is \c{noexcept} only if \c{E} is \c{std::dynamic_extent}. + + \note If \c{E} is not \c{std::dynamic_extent} and the size of \a il is not \c{E}, the behavior is undefined. +*/ + // // Member functions: sizes // diff --git a/tests/auto/corelib/tools/qspan/tst_qspan.cpp b/tests/auto/corelib/tools/qspan/tst_qspan.cpp index 52f5d6362af..91d2ecf739a 100644 --- a/tests/auto/corelib/tools/qspan/tst_qspan.cpp +++ b/tests/auto/corelib/tools/qspan/tst_qspan.cpp @@ -99,6 +99,15 @@ static_assert(!std::is_convertible_v, std::span>); // Spans don't convert from nonsense: static_assert(!std::is_constructible_v, int&&>); +// Span is constructible from initializer_list +static_assert( std::is_convertible_v, QSpan>); +static_assert(!std::is_convertible_v, QSpan< int>>); +static_assert(!std::is_constructible_v, std::initializer_list>); + +static_assert( std::is_convertible_v, QSpan>); // non-standard, but QSpan considers initializer_list a range +static_assert( std::is_constructible_v, std::initializer_list>); +static_assert(!std::is_constructible_v, std::initializer_list>); + class tst_QSpan : public QObject { Q_OBJECT @@ -114,6 +123,7 @@ private Q_SLOTS: void fromZeroSizeStdArray() const; void fromStdVector() const; void fromQList() const; + void fromInitList() const; private: template @@ -322,9 +332,11 @@ 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); + + using V = std::remove_reference_t>; { QSpan si = c; // CTAD - static_assert(std::is_same_v>); + static_assert(std::is_same_v>); QCOMPARE_EQ(si.size(), c_size); QCOMPARE_EQ(si.data(), c_data); @@ -421,6 +433,17 @@ void tst_QSpan::fromQList() const from_variable_size_container_impl(li); } +void tst_QSpan::fromInitList() const +{ + from_variable_size_container_impl(std::initializer_list{42, 84, 168, 336}); + + auto l1 = [](QSpan){}; + l1({1, 2, 3}); + + auto l2 = [](QSpan){}; + l2({4, 5, 6}); +} + #undef RETURN_IF_FAILED QTEST_APPLESS_MAIN(tst_QSpan);