[PATCH] libstdc++: add initializer_list constructor to std::span (P2447)

Jonathan Wakely jwakely@redhat.com
Wed Dec 11 22:52:43 GMT 2024


On 03/12/24 17:05 +0100, Giuseppe D'Angelo wrote:
>Hello,
>
>The attached patch adds the span(initializer_list) constructor, added 
>by P2447R6 for C++26.
>
>It seems that GCC is somewhat aggressive with its -Winit-list-lifetime 
>warning, which actively interferes with this feature. The idea of the 
>new constructor is to allow calls like:
>
>  void f(std::span<const int>);
>  f({1, 2, 3});
>
>which is completely fine as the lifetime of the initializer_list 
>encompasses the one of the std::span parameter. However GCC complains 
>about the risk of dangling here. I've therefore disabled the warning 
>for the new constructor.
>
>Thanks,
>-- 
>Giuseppe D'Angelo

>From bb1d537ee7b68883403127903834427c6787c505 Mon Sep 17 00:00:00 2001
>From: Giuseppe D'Angelo <giuseppe.dangelo@kdab.com>
>Date: Tue, 3 Dec 2024 16:56:45 +0100
>Subject: [PATCH] libstdc++: add initializer_list constructor to std::span
> (P2447)
>
>This commit implements P2447R6. The code is straightforward (just one
>extra constructor, with constraints and conditional explicit).
>
>I decided to suppress -Winit-list-lifetime because otherwise it would
>give too many false positives. The new constructor is meant to be used
>as a parameter-passing interface (this is a design choice, see
>P2447R6/2) and, as such, the initializer_list won't dangle despite GCC's
>warnings.
>
>The new constructor isn't 100% backwards compatible. A couple of
>examples are included in Annex C, but I have also lifted some more
>from R4. A new test checks for the old and the new behaviors.
>
>libstdc++-v3/ChangeLog:
>
>	* include/bits/version.def: Added the new feature-testing macro.
>	* include/bits/version.h (defined): Regenerated.
>	* include/std/span: Added constructor from initializer_list.
>	* testsuite/23_containers/span/init_list_cons.cc: New test.
>	* testsuite/23_containers/span/init_list_cons_neg.cc: New test.
>---
> libstdc++-v3/include/bits/version.def         |  8 +++
> libstdc++-v3/include/bits/version.h           | 10 +++
> libstdc++-v3/include/std/span                 | 21 ++++++
> .../23_containers/span/init_list_cons.cc      | 65 +++++++++++++++++++
> .../23_containers/span/init_list_cons_neg.cc  | 31 +++++++++
> 5 files changed, 135 insertions(+)
> create mode 100644 libstdc++-v3/testsuite/23_containers/span/init_list_cons.cc
> create mode 100644 libstdc++-v3/testsuite/23_containers/span/init_list_cons_neg.cc
>
>diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>index 8d4b8e9b383..cfa0469fb2d 100644
>--- a/libstdc++-v3/include/bits/version.def
>+++ b/libstdc++-v3/include/bits/version.def
>@@ -1853,6 +1853,14 @@ ftms = {
>   };
> };
> 
>+ftms = {
>+  name = span_initializer_list;
>+  values = {
>+    v = 202311;
>+    cxxmin = 26;
>+  };
>+};
>+
> ftms = {
>   name = text_encoding;
>   values = {
>diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>index c556aca38fa..6a2c66bdf81 100644
>--- a/libstdc++-v3/include/bits/version.h
>+++ b/libstdc++-v3/include/bits/version.h
>@@ -2055,6 +2055,16 @@
> #endif /* !defined(__cpp_lib_saturation_arithmetic) && defined(__glibcxx_want_saturation_arithmetic) */
> #undef __glibcxx_want_saturation_arithmetic
> 
>+#if !defined(__cpp_lib_span_initializer_list)
>+# if (__cplusplus >  202302L)
>+#  define __glibcxx_span_initializer_list 202311L
>+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_span_initializer_list)
>+#   define __cpp_lib_span_initializer_list 202311L
>+#  endif
>+# endif
>+#endif /* !defined(__cpp_lib_span_initializer_list) && defined(__glibcxx_want_span_initializer_list) */
>+#undef __glibcxx_want_span_initializer_list
>+
> #if !defined(__cpp_lib_text_encoding)
> # if (__cplusplus >  202302L) && _GLIBCXX_HOSTED && (_GLIBCXX_USE_NL_LANGINFO_L)
> #  define __glibcxx_text_encoding 202306L
>diff --git a/libstdc++-v3/include/std/span b/libstdc++-v3/include/std/span
>index f1c19b58737..b84ac87b657 100644
>--- a/libstdc++-v3/include/std/span
>+++ b/libstdc++-v3/include/std/span
>@@ -39,6 +39,7 @@
> #endif
> 
> #define __glibcxx_want_span
>+#define __glibcxx_want_span_initializer_list
> #include <bits/version.h>
> 
> #ifdef __cpp_lib_span // C++ >= 20 && concepts
>@@ -46,6 +47,9 @@
> #include <cstddef>
> #include <bits/stl_iterator.h>
> #include <bits/ranges_base.h>
>+#ifdef __cpp_lib_span_initializer_list
>+# include <initializer_list>
>+#endif
> namespace std _GLIBCXX_VISIBILITY(default)
> {
> _GLIBCXX_BEGIN_NAMESPACE_VERSION
>@@ -226,6 +230,23 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
> 	    }
> 	}
> 
>+#if __cpp_lib_span_initializer_list >= 202311L
>+#pragma GCC diagnostic push
>+#pragma GCC diagnostic ignored "-Winit-list-lifetime"
>+	constexpr
>+	explicit(extent != dynamic_extent)
>+	span(initializer_list<value_type> __il)
>+	requires (is_const_v<_Type>)
>+	: _M_extent(__il.size()), _M_ptr(__il.begin())

These mem-initializers are in the wrong order (we had an existing
constructor with the same problem, but I pushed a fix less than an
hour ago).

>+	{
>+	  if constexpr (extent != dynamic_extent)
>+	    {
>+	      __glibcxx_assert(__il.size() == extent);

It's just occurred to me that this check should be done in one place,
not in every constructor. The __detail::__extent_storage<extent>
constructor can check it.

I'll prepare a patch to do that, please just remove this if-constexpr
block and the __glibcxx_assert, just saying _M_extent(__il.size())
will include the assertion soon :-)

With those two changes, this looks OK to push (disabling the warning
for now is our only option, but I'll continue looking into whether we
can re-enable it and have the front end do the right thing).

>+	    }
>+	}
>+#pragma GCC diagnostic pop
>+#endif
>+
>       constexpr
>       span(const span&) noexcept = default;
> 
>diff --git a/libstdc++-v3/testsuite/23_containers/span/init_list_cons.cc b/libstdc++-v3/testsuite/23_containers/span/init_list_cons.cc
>new file mode 100644
>index 00000000000..7aabbf1e809
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/span/init_list_cons.cc
>@@ -0,0 +1,65 @@
>+// { dg-do compile { target c++26 } }
>+
>+#include <span>
>+#include <type_traits>
>+
>+#if !defined(__cpp_lib_span_initializer_list)
>+# error "__cpp_lib_span_initializer_list should be defined"
>+#elif __cpp_lib_span_initializer_list < 202311L
>+# error "Wrong value for __cpp_lib_span_initializer_list (should be >= 202311L)"
>+#endif
>+
>+// Check the constraint on the initialier_list constructor
>+static_assert( std::is_const_v<std::span<const int>::element_type>);
>+static_assert(!std::is_const_v<std::span<      int>::element_type>);
>+
>+static_assert( std::is_constructible_v<std::span<const int    >, std::initializer_list<      int>>);
>+static_assert( std::is_constructible_v<std::span<const int    >, std::initializer_list<const int>>);
>+static_assert( std::is_constructible_v<std::span<const int, 42>, std::initializer_list<      int>>);
>+static_assert( std::is_constructible_v<std::span<const int, 42>, std::initializer_list<const int>>);
>+static_assert(!std::is_constructible_v<std::span<      int    >, std::initializer_list<      int>>);
>+static_assert(!std::is_constructible_v<std::span<      int    >, std::initializer_list<const int>>);
>+static_assert(!std::is_constructible_v<std::span<      int, 42>, std::initializer_list<      int>>);
>+static_assert(!std::is_constructible_v<std::span<      int, 42>, std::initializer_list<const int>>);
>+
>+// Check the explicit-ness on the initialier_list constructor
>+static_assert( std::is_convertible_v<std::initializer_list<      int>, std::span<const int    >>);
>+static_assert( std::is_convertible_v<std::initializer_list<const int>, std::span<const int    >>);
>+static_assert(!std::is_convertible_v<std::initializer_list<      int>, std::span<const int, 42>>);
>+static_assert(!std::is_convertible_v<std::initializer_list<const int>, std::span<const int, 42>>);
>+static_assert(!std::is_convertible_v<std::initializer_list<      int>, std::span<      int    >>);
>+static_assert(!std::is_convertible_v<std::initializer_list<const int>, std::span<      int    >>);
>+static_assert(!std::is_convertible_v<std::initializer_list<      int>, std::span<      int, 42>>);
>+static_assert(!std::is_convertible_v<std::initializer_list<const int>, std::span<      int, 42>>);
>+
>+constexpr size_t fun1(std::span<const int> s)
>+{
>+  return s.size();
>+}
>+
>+static_assert(fun1({}) == 0);
>+static_assert(fun1({1, 2, 3}) == 3);
>+static_assert(fun1(std::initializer_list<int>{1, 2, 3}) == 3);
>+
>+// Stress-test array->pointer decays
>+struct decayer {
>+  constexpr decayer() = default;
>+  constexpr decayer(decayer *) {}
>+};
>+
>+constexpr size_t fun2(std::span<const decayer> s)
>+{
>+  return s.size();
>+}
>+
>+void test01()
>+{
>+  int intArray[42];
>+  static_assert(fun1(intArray) == 42);
>+
>+  decayer decArray[42];
>+  static_assert(fun2(decArray) == 42);
>+  static_assert(fun2({decArray}) == 1); // decayer[] -> decayer* -> decayer(decayer*) -> init_list<decayer> of 1 element
>+  static_assert(fun2({decArray, decArray + 42}) == 2); // does not select span(iterator, iterator)
>+  static_assert(fun2({decArray, decArray, decArray}) == 3);
>+}
>diff --git a/libstdc++-v3/testsuite/23_containers/span/init_list_cons_neg.cc b/libstdc++-v3/testsuite/23_containers/span/init_list_cons_neg.cc
>new file mode 100644
>index 00000000000..ef43541b769
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/23_containers/span/init_list_cons_neg.cc
>@@ -0,0 +1,31 @@
>+// { dg-do run { target c++20 } }
>+
>+#include <span>
>+#include <utility>
>+#include <any>
>+
>+#include <testsuite_hooks.h>
>+
>+// Examples from P2447R4
>+void one(std::pair<int, int>) {}
>+void one(std::span<const int>) {}
>+void two(std::span<const int, 2>) {}
>+constexpr std::size_t three(std::span<void * const> v) { return v.size(); }
>+constexpr std::size_t four(std::span<const std::any> v) { return v.size(); }
>+
>+int main()
>+{
>+  one({1, 2}); // { dg-error "call of overloaded" "should be ambiguous with the one(std::pair) overload" { target c++26 } }
>+  two({{1, 2}}); // { dg-error "would use explicit constructor" "should prefer the initializer_list constructor, which is explicit" { target c++26 } }
>+
>+  void *array3[10];
>+  std::any array4[10];
>+
>+#if __cpp_lib_span_initializer_list
>+  static_assert(three({array3, 0}) == 2);
>+  VERIFY(four({array4, array4 + 10}) == 2);
>+#else
>+  static_assert(three({array3, 0}) == 0);
>+  VERIFY(four({array4, array4 + 10}) == 10);
>+#endif
>+}
>-- 
>2.34.1
>





More information about the Libstdc++ mailing list