QSpan: add construction from initializer_list

P2447 has been merged in C++26, backport the same functionality.

This makes QSpan<const T> a proper replacement for a const QList<T>&
parameter, because now both can be built via a braced-init-list.

  // void f(const QList<int> &l); // old
  void f(QSpan<const int>);       // 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<const int> 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 <marc.mutz@qt.io>
(cherry picked from commit 7f7b5ff3a1b617a3a1add1b1b6ad0718f0dcf143)
Reviewed-by: Qt Cherry-pick Bot <cherrypick_bot@qt-project.org>
This commit is contained in:
Giuseppe D'Angelo 2024-03-04 15:33:09 +01:00 committed by Qt Cherry-pick Bot
parent 9ea3087d36
commit c80f475055
3 changed files with 48 additions and 1 deletions

View File

@ -11,6 +11,7 @@
#include <array>
#include <cstddef>
#include <cassert>
#include <initializer_list>
#include <QtCore/q20iterator.h>
#include <QtCore/q20memory.h>
#ifdef __cpp_lib_span
@ -224,6 +225,11 @@ public:
: QSpanBase(other.data(), other.size())
{}
template <typename U = T, std::enable_if_t<std::is_const_v<U>, bool> = true>
Q_IMPLICIT constexpr QSpanBase(std::initializer_list<std::remove_cv_t<T>> il)
: QSpanBase(il.begin(), il.size())
{}
#ifdef __cpp_lib_span
template <typename S, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::span<S, E> other) noexcept
@ -286,6 +292,11 @@ public:
: QSpanBase(other.data(), other.size())
{}
template <typename U = T, std::enable_if_t<std::is_const_v<U>, bool> = true>
Q_IMPLICIT constexpr QSpanBase(std::initializer_list<std::remove_cv_t<T>> il) noexcept
: QSpanBase(il.begin(), il.size())
{}
#if __cpp_lib_span
template <typename S, size_t N, if_qualification_conversion<S> = true>
Q_IMPLICIT constexpr QSpanBase(std::span<S, N> other) noexcept
@ -347,6 +358,7 @@ public:
template <typename Range, if_compatible_range<Range> = true> constexpr QSpan(Range &&r);
template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(QSpan<S, N> other) noexcept;
template <typename S, size_t N, if_qualification_conversion<S> = true> constexpr QSpan(std::span<S, N> other) noexcept;
constexpr QSpan(std::initializer_list<value_type> il);
#endif // Q_QDOC
// [span.obs]

View File

@ -341,6 +341,18 @@
\endlist
*/
/*!
\fn template <typename T, size_t E> QSpan<T, E>::QSpan(std::initializer_list<value_type> 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
//

View File

@ -99,6 +99,15 @@ static_assert(!std::is_convertible_v<QSpan<const int, 0>, std::span<int, 0>>);
// Spans don't convert from nonsense:
static_assert(!std::is_constructible_v<QSpan<const int>, int&&>);
// Span is constructible from initializer_list
static_assert( std::is_convertible_v<std::initializer_list<int>, QSpan<const int>>);
static_assert(!std::is_convertible_v<std::initializer_list<int>, QSpan< int>>);
static_assert(!std::is_constructible_v<QSpan<int>, std::initializer_list<int>>);
static_assert( std::is_convertible_v<std::initializer_list<int>, QSpan<const int, 4>>); // non-standard, but QSpan considers initializer_list a range
static_assert( std::is_constructible_v<QSpan<const int, 4>, std::initializer_list<int>>);
static_assert(!std::is_constructible_v<QSpan< int, 4>, std::initializer_list<int>>);
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 <typename T, std::size_t N>
@ -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<QSpanPrivate::range_reference_t<C>>;
{
QSpan si = c; // CTAD
static_assert(std::is_same_v<decltype(si), QSpan<int, ExpectedExtent>>);
static_assert(std::is_same_v<decltype(si), QSpan<V, ExpectedExtent>>);
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<int>{42, 84, 168, 336});
auto l1 = [](QSpan<const int>){};
l1({1, 2, 3});
auto l2 = [](QSpan<const int, 3>){};
l2({4, 5, 6});
}
#undef RETURN_IF_FAILED
QTEST_APPLESS_MAIN(tst_QSpan);