[PATCH] libstdc++: Add data/member for initializer_list, and remove std overloads

Jonathan Wakely jwakely@redhat.com
Thu May 21 12:40:33 GMT 2026


On Mon, 18 May 2026 at 13:43 +0200, Tomasz Kamiński wrote:
>This implements changes section 4.6 of P3016R6, and initializer_list related
>parts of 4.7. The change makes immediately dangling invocations of std::begin,
>std::end, and std::data on braced-init list ill-formed (see range_access_neg.cc
>and range_access17_neg.cc):
>  auto it = std::begin({1, 2, 3});  // ILL-FORMED, it was dangling
>  (it == std::end({1, 2, 3}));      // ILL-FORMED, was unspecified
>  auto* ptr = std::data({1, 2, 3}); // ILL-FORMED, ptr was dangling
>However, similary problemetic calls for std::rbegin, std::rend remain
>well-formed (see range_access14_neg.cc), as initializer_list overloads
>are preserved for these functions:
>  auto rit = ranges::rbegin({1, 2, 3}); // COMPILES, dangling
>  auto rend = ranges::rend({1, 2, 3});  // COMPILES, danging
>Note, that non-problematic std::size({1, 2, 3}) and std::empty({1, 2, 3})
>use c-array overloads, and remain well-formed.
>
>Per paper, to keep std::data(il) and std::empty(il) well-formed, the data
>and empty member are added to initializer_list.
>
>libstdc++-v3/ChangeLog:
>
>	* include/bits/version.def (initializer_list): Define with value
>	202511 for C++26.
>	* include/bits/version.h: Regenerate.
>	* libsupc++/initializer_list (initializer_list::data)
>	(initializer_list::empty) [__glibcxx_initializer_list >= 202511L]:
>	Define.
>	(std::begin(initializer_list<_Tp>), std::end(initializer_list<_Tp>)):
>	Define only if __glibcxx_initializer_list < 202511L (i.e. not defined).
>	* include/bits/range_access.h (std::empty(initializer_list<_Tp>))
>	(std::data(initializer_list<_Tp>)): Define only if
>	__glibcxx_initializer_list < 202511L (i.e. not defined).
>	* testsuite/18_support/initializer_list/range_access.cc: Move test for
>	brace-init list to range_access_neg.c. Included <iterator> in C++26 or
>	later mode.
>	* testsuite/18_support/initializer_list/data_empty_mem.cc: New test.
>	* testsuite/18_support/initializer_list/range_access14.cc: New test.
>	* testsuite/18_support/initializer_list/range_access14_neg.cc: New test.
>	* testsuite/18_support/initializer_list/range_access17.cc: New test.
>	* testsuite/18_support/initializer_list/range_access17_neg.cc: New test.
>	* testsuite/18_support/initializer_list/range_access_neg.cc: New test.
>---
>I was experimenting with backporting the change up to C++11, as a fix for
>always dangling std::begin({....}), but found out that std::begin(il) (for il
>being initializer_list) becomes non-constexpr until C++17. This could be
>addressed by backporting constexpr on std::begin(Container). But the paper
>does not fully resolve the issue: std::rbegin({...}) is still well-formed.
>
>Making this C++26 change then. We could still backport data/empty members,
>but waiting to see responses for other implementers.
>
>Testing on x86_64-linux. OK for trunk when all tests passes?

OK

> libstdc++-v3/include/bits/range_access.h      |  4 ++++
> libstdc++-v3/include/bits/version.def         |  9 +++++++
> libstdc++-v3/include/bits/version.h           | 10 ++++++++
> libstdc++-v3/libsupc++/initializer_list       | 13 ++++++++++
> .../initializer_list/data_empty_mem.cc        | 21 ++++++++++++++++
> .../initializer_list/range_access.cc          | 10 +++-----
> .../initializer_list/range_access14.cc        | 24 +++++++++++++++++++
> .../initializer_list/range_access14_neg.cc    | 18 ++++++++++++++
> .../initializer_list/range_access17.cc        | 16 +++++++++++++
> .../initializer_list/range_access17_neg.cc    | 14 +++++++++++
> .../initializer_list/range_access_neg.cc      | 13 ++++++++++
> 11 files changed, 145 insertions(+), 7 deletions(-)
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/data_empty_mem.cc
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/range_access14.cc
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/range_access14_neg.cc
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/range_access17.cc
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/range_access17_neg.cc
> create mode 100644 libstdc++-v3/testsuite/18_support/initializer_list/range_access_neg.cc
>
>diff --git a/libstdc++-v3/include/bits/range_access.h b/libstdc++-v3/include/bits/range_access.h
>index 5a748257f19..b89129f0233 100644
>--- a/libstdc++-v3/include/bits/range_access.h
>+++ b/libstdc++-v3/include/bits/range_access.h
>@@ -328,6 +328,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     empty(const _Tp (&)[_Nm]) noexcept
>     { return false; }
>
>+#if __glibcxx_initializer_list < 202511L
>   /**
>    *  @brief  Return whether an initializer_list is empty.
>    *  @param  __il  Initializer list.
>@@ -338,6 +339,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     constexpr bool
>     empty(initializer_list<_Tp> __il) noexcept
>     { return __il.size() == 0;}
>+#endif
>
>   /**
>    *  @brief  Return the data pointer of a container.
>@@ -374,6 +376,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     data(_Tp (&__array)[_Nm]) noexcept
>     { return __array; }
>
>+#if __glibcxx_initializer_list < 202511L
>   /**
>    *  @brief  Return the data pointer of an initializer list.
>    *  @param  __il  Initializer list.
>@@ -384,6 +387,7 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>     constexpr const _Tp*
>     data(initializer_list<_Tp> __il) noexcept
>     { return __il.begin(); }
>+#endif
> #endif // __glibcxx_nonmember_container_access
>
> #ifdef __glibcxx_ssize // C++ >= 20
>diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def
>index 1f0d3a2670e..efcc0852af5 100644
>--- a/libstdc++-v3/include/bits/version.def
>+++ b/libstdc++-v3/include/bits/version.def
>@@ -2410,6 +2410,15 @@ ftms = {
>   };
> };
>
>+ftms = {
>+  name = initializer_list;
>+  values = {
>+    v = 202511;
>+    cxxmin = 26;
>+  };
>+};
>+
>+
> // Standard test specifications.
> stds[97] = ">= 199711L";
> stds[03] = ">= 199711L";
>diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h
>index 66ac0ebef68..9402f25df37 100644
>--- a/libstdc++-v3/include/bits/version.h
>+++ b/libstdc++-v3/include/bits/version.h
>@@ -2675,4 +2675,14 @@
> #endif /* !defined(__cpp_lib_is_structural) */
> #undef __glibcxx_want_is_structural
>
>+#if !defined(__cpp_lib_initializer_list)
>+# if (__cplusplus >  202302L)
>+#  define __glibcxx_initializer_list 202511L
>+#  if defined(__glibcxx_want_all) || defined(__glibcxx_want_initializer_list)
>+#   define __cpp_lib_initializer_list 202511L
>+#  endif
>+# endif
>+#endif /* !defined(__cpp_lib_initializer_list) */
>+#undef __glibcxx_want_initializer_list
>+
> #undef __glibcxx_want_all
>diff --git a/libstdc++-v3/libsupc++/initializer_list b/libstdc++-v3/libsupc++/initializer_list
>index baf47baa5f8..fbea49dfb01 100644
>--- a/libstdc++-v3/libsupc++/initializer_list
>+++ b/libstdc++-v3/libsupc++/initializer_list
>@@ -40,6 +40,9 @@
>
> #include <bits/c++config.h>
>
>+#define __glibcxx_want_initializer_list
>+#include <bits/version.h>
>+
> namespace std _GLIBCXX_VISIBILITY(default)
> {
>   /// initializer_list
>@@ -77,8 +80,17 @@ namespace std _GLIBCXX_VISIBILITY(default)
>       // One past the last element.
>       constexpr const_iterator
>       end() const noexcept { return begin() + size(); }
>+
>+#if __glibcxx_initializer_list >= 202511L
>+      constexpr bool
>+      empty() const noexcept { return _M_len == 0; }
>+
>+      constexpr const value_type*
>+      data() const noexcept { return _M_array; }
>+#endif
>     };
>
>+#if __glibcxx_initializer_list < 202511L
>   /**
>    *  @brief  Return an iterator pointing to the first element of
>    *          the initializer_list.
>@@ -100,6 +112,7 @@ namespace std _GLIBCXX_VISIBILITY(default)
>     constexpr const _Tp*
>     end(initializer_list<_Tp> __ils) noexcept
>     { return __ils.end(); }
>+#endif // __glibcxx_initializer_list < 202511L
> }
>
> #endif // C++11
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/data_empty_mem.cc b/libstdc++-v3/testsuite/18_support/initializer_list/data_empty_mem.cc
>new file mode 100644
>index 00000000000..d5673ebf592
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/data_empty_mem.cc
>@@ -0,0 +1,21 @@
>+// { dg-do compile { target c++26 } }
>+
>+#include <initializer_list>
>+#include <iterator>
>+
>+#ifndef __cpp_lib_initializer_list
>+# error "Feature-test macro for text_encoding missing in <initializer_list>"
>+#elif __cpp_lib_initializer_list != 202511L
>+# error "Feature-test macro for text_encoding has wrong value in <initializer_list>"
>+#endif
>+
>+void
>+test02()
>+{
>+  static constexpr std::initializer_list<int> il{1};
>+  static_assert( il.data() == il.begin() );
>+  static_assert( il.empty() == false );
>+  static_assert( noexcept(il.data()) );
>+  static_assert( noexcept(il.empty()) );
>+}
>+
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access.cc
>index 8d7cb0dfb3f..feffd4ee4e4 100644
>--- a/libstdc++-v3/testsuite/18_support/initializer_list/range_access.cc
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access.cc
>@@ -20,16 +20,12 @@
> // 18.9.3 Initializer list range access [support.initlist.range]
>
> #include <initializer_list>
>+#if __cpp_lib_initializer_list >= 202511L
>+#  include <iterator>
>+#endif
>
> void
> test01()
>-{
>-  std::begin({1, 2, 3});
>-  std::end({1, 2, 3});
>-}
>-
>-void
>-test02()
> {
>   static constexpr std::initializer_list<int> il{1};
>   static_assert( std::begin(il) == il.begin() );
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access14.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access14.cc
>new file mode 100644
>index 00000000000..b98e9ccf7b5
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access14.cc
>@@ -0,0 +1,24 @@
>+// { dg-do compile { target c++14 } }
>+
>+#include <initializer_list>
>+#include <iterator>
>+
>+void
>+test01()
>+{
>+  static constexpr std::initializer_list<int> il{1, 2};
>+  static_assert( std::cbegin(il) == il.begin() );
>+  static_assert( std::cend(il) == il.end() );
>+#if __cplusplus >= 201703L
>+  static_assert( std::rbegin(il).base() == il.end() );
>+  static_assert( std::rend(il).base() == il.begin() );
>+  static_assert( std::rbegin(il).base() == il.end() );
>+  static_assert( std::rend(il).base() == il.begin() );
>+#endif
>+  static_assert( noexcept(std::cbegin(il)) );
>+  static_assert( noexcept(std::cend(il)) );
>+  static_assert( noexcept(std::rbegin(il)) );
>+  static_assert( noexcept(std::rend(il)) );
>+  static_assert( noexcept(std::crbegin(il)) );
>+  static_assert( noexcept(std::crend(il)) );
>+}
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access14_neg.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access14_neg.cc
>new file mode 100644
>index 00000000000..e1c4062fc67
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access14_neg.cc
>@@ -0,0 +1,18 @@
>+// { dg-do compile { target c++17 } }
>+
>+#include <iterator>
>+
>+void
>+test01()
>+{
>+  (void)std::cbegin({1, 2, 3});  // { dg-error "no matching function for call" }
>+  (void)std::cend({1, 2, 3});    // { dg-error "no matching function for call" }
>+  (void)std::rbegin({1, 2, 3});  // initializer_list overload not removed
>+  (void)std::rend({1, 2, 3});    // initializer_list overload not removed
>+  (void)std::crbegin({1, 2, 3}); // { dg-error "no matching function for call" }
>+  (void)std::crend({1, 2, 3});   // { dg-error "no matching function for call" }
>+
>+}
>+
>+// { dg-prune-output "cannot bind non-const lvalue reference of type" }
>+// { dg-prune-output "which is of non-class type" }
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access17.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access17.cc
>new file mode 100644
>index 00000000000..443aae3aaa0
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access17.cc
>@@ -0,0 +1,16 @@
>+// { dg-do compile { target c++17 } }
>+
>+#include <initializer_list>
>+#include <iterator>
>+
>+void
>+test01()
>+{
>+  static constexpr std::initializer_list<int> il{1};
>+  static_assert( std::data(il) == il.begin() );
>+  static_assert( std::size(il) == il.size() );
>+  static_assert( !std::empty(il) );
>+  static_assert( noexcept(std::data(il)) );
>+  static_assert( noexcept(std::size(il)) );
>+  static_assert( noexcept(std::empty(il)) );
>+}
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access17_neg.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access17_neg.cc
>new file mode 100644
>index 00000000000..fbe18f78bf8
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access17_neg.cc
>@@ -0,0 +1,14 @@
>+// { dg-do compile { target c++17 } }
>+
>+#include <iterator>
>+
>+void
>+test01()
>+{
>+  (void)std::data({1, 2, 3});  // { dg-error "no matching function for call" "" { target c++26 } }
>+  (void)std::size({1, 2, 3});  // uses array overload
>+  (void)std::empty({1, 2, 3}); // uses array overload
>+}
>+
>+// { dg-prune-output "cannot bind non-const lvalue reference of type" }
>+// { dg-prune-output "which is of non-class type" }
>diff --git a/libstdc++-v3/testsuite/18_support/initializer_list/range_access_neg.cc b/libstdc++-v3/testsuite/18_support/initializer_list/range_access_neg.cc
>new file mode 100644
>index 00000000000..e4bff4ecbed
>--- /dev/null
>+++ b/libstdc++-v3/testsuite/18_support/initializer_list/range_access_neg.cc
>@@ -0,0 +1,13 @@
>+// { dg-do compile { target c++11 } }
>+
>+#include <iterator>
>+
>+void
>+test01()
>+{
>+  (void)std::begin({1, 2, 3}); // { dg-error "no matching function for call" "" { target c++26 } }
>+  (void)std::end({1, 2, 3});   // { dg-error "no matching function for call" "" { target c++26 } }
>+}
>+
>+// { dg-prune-output "cannot bind non-const lvalue reference of type" }
>+// { dg-prune-output "which is of non-class type" }
>-- 
>2.54.0
>
>



More information about the Libstdc++ mailing list