[PATCH v1 4/4] libstdc++: Implement submdspan_extents. [PR110352]

Luc Grosheintz luc.grosheintz@gmail.com
Thu Oct 2 09:27:17 GMT 2025


Implement submdspan_extents as described in N5014 and adds it to the std
module.

The implementation contains a casting or canonicalization layer.
Similar, but different to the one described in P3663. It's purpose is
twofold:

  - Reduce the number of template instantiations and isolate the
    complexity of dealing with user-defined integer like types to this
    layer.

  - Handle special user-defined types that only convert to index_type
    as rvalues, etc.

The rules for canonicalization are:

  - collapsing slices, i.e. those that convert to index_type, are
    canonicalized as follows:

    + integral-constant-like objects are canonicalized to the corresponding
      integral_constant<index_type, ...> object,

    + everything else is canonicalized via
      static_cast<index_type>(std::move(...)),

  - for any strided_slice it's the analogous strided_slice with all
    three members canonicalized,

  - index-pair-like objects are converted to std::tuple with both
    elements canonicalized,

  - anything convertable to full_extent_t is canonicalized to
    full_extent.

	PR libstdc++/110352

libstdc++-v3/ChangeLog:

	* include/std/mdspan (submdspan_extents): New function.
	* testsuite/23_containers/mdspan/int_like.h: Add StructuralInt.
	* testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc: New test.
	* testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc: New test.

Signed-off-by: Luc Grosheintz <luc.grosheintz@gmail.com>
---
 libstdc++-v3/include/std/mdspan               | 276 ++++++++++++++++++
 .../testsuite/23_containers/mdspan/int_like.h |   9 +
 .../mdspan/submdspan/submdspan_extents.cc     | 159 ++++++++++
 .../mdspan/submdspan/submdspan_extents_neg.cc |  66 +++++
 4 files changed, 510 insertions(+)
 create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
 create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc

diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan
index cef6d625982..a88d4f8ff76 100644
--- a/libstdc++-v3/include/std/mdspan
+++ b/libstdc++-v3/include/std/mdspan
@@ -40,6 +40,7 @@
 
 #if __cplusplus > 202302L
 #include <bits/align.h>
+#include <tuple>
 #endif
 
 #define __glibcxx_want_mdspan
@@ -368,6 +369,123 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
       [[no_unique_address]] _Mapping mapping = _Mapping();
       size_t offset{};
     };
+
+  namespace __mdspan
+  {
+    template<typename _Tp>
+      constexpr _Tp
+      __unwrap_constant(_Tp __value)
+      { return __value; }
+
+    template<__detail::__integral_constant_like _Tp>
+      constexpr auto
+      __unwrap_constant(_Tp)
+      { return _Tp::value; }
+
+    template<typename _Tp, typename _IndexType>
+      concept __index_pair_like = __pair_like<_Tp>
+	&& convertible_to<tuple_element_t<0,_Tp>, _IndexType>
+	&& convertible_to<tuple_element_t<1,_Tp>, _IndexType>;
+
+    template<typename _Tp>
+      inline constexpr bool __is_strided_slice = false;
+
+    template<typename _OffsetType, typename _ExtentType, typename _StrideType>
+      inline constexpr bool __is_strided_slice<strided_slice<_OffsetType,
+	  _ExtentType, _StrideType>> = true;
+
+    template<typename _IndexType, typename... _Slices>
+      consteval auto __submdspan_rank()
+      {
+	return (static_cast<size_t>(!convertible_to<_Slices, _IndexType>)
+		+ ... + 0);
+      }
+
+    template<typename _IndexType, typename... _Slices>
+      consteval auto __inv_map_rank()
+      {
+	constexpr auto __rank = sizeof...(_Slices);
+	constexpr auto __sub_rank = __submdspan_rank<_IndexType, _Slices...>();
+	auto __map = std::array<size_t, __sub_rank>{};
+	auto __is_int_like = std::array{convertible_to<_Slices, _IndexType>...};
+
+	size_t __i = 0;
+	for (size_t __k = 0; __k < __rank; ++__k)
+	  if (!__is_int_like[__k])
+	    __map[__i++] = __k;
+	return __map;
+      }
+
+    template<typename _IndexType, typename _Slice>
+      constexpr auto
+      __slice_cast(_Slice&& __slice)
+      {
+	if constexpr (convertible_to<_Slice, _IndexType>
+		      && __detail::__integral_constant_like<_Slice>)
+	  return integral_constant<_IndexType, _IndexType{_Slice::value}>{};
+	else if constexpr (convertible_to<_Slice, _IndexType>)
+	  return __index_type_cast<_IndexType>(std::move(__slice));
+	else if constexpr (__index_pair_like<_Slice, _IndexType>)
+	  {
+	    auto [__begin, __end] = std::move(__slice);
+	    return std::tuple{__slice_cast<_IndexType>(std::move(__begin)),
+			      __slice_cast<_IndexType>(std::move(__end))};
+	  }
+	else if constexpr (__is_strided_slice<_Slice>)
+	  return strided_slice{
+	    __slice_cast<_IndexType>(std::move(__slice.offset)),
+	    __slice_cast<_IndexType>(std::move(__slice.extent)),
+	    __slice_cast<_IndexType>(std::move(__slice.stride))};
+	else
+	  return full_extent;
+      }
+
+    template<typename _IndexType, typename _Slice>
+      constexpr _IndexType
+      __slice_begin(_Slice __slice)
+      {
+	if constexpr (convertible_to<_Slice, _IndexType>)
+	  return __slice;
+	else if constexpr (__index_pair_like<_Slice, _IndexType>)
+	  return get<0>(__slice);
+	else if constexpr (__is_strided_slice<_Slice>)
+	  return __slice.offset;
+	else
+	  return 0; // full_extent
+      }
+
+    template<size_t _K, typename _Extents, typename _Slice>
+      constexpr typename _Extents::index_type
+      __slice_end(_Extents __exts, _Slice __slice)
+      {
+	using _IndexType = typename _Extents::index_type;
+	if constexpr (convertible_to<_Slice, _IndexType>)
+	  return __unwrap_constant(__slice) + 1;
+	else if constexpr (__index_pair_like<_Slice, _IndexType>)
+	  return get<1>(__slice);
+	else if constexpr (__is_strided_slice<_Slice>)
+	  return __unwrap_constant(__slice.offset)
+		 + __unwrap_constant(__slice.extent);
+	else
+	  return __exts.extent(_K); // full_extent
+      }
+
+    template<size_t _K, typename _Extents, typename _Slice>
+      constexpr typename _Extents::index_type
+      __slice_size(const _Extents& __exts, _Slice __slice)
+      {
+	using _IndexType = typename _Extents::index_type;
+	if constexpr (convertible_to<_Slice, _IndexType>)
+	  return 1;
+	else if constexpr (__index_pair_like<_Slice, _IndexType>)
+	  return __unwrap_constant(get<1>(__slice))
+		 - __unwrap_constant(get<0>(__slice));
+	else if constexpr (__is_strided_slice<_Slice>)
+	  return __unwrap_constant(__slice.extent);
+	else
+	  return __exts.extent(_K);
+      }
+  }
 #endif
 
   template<typename _IndexType, size_t... _Extents>
@@ -2492,6 +2610,164 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
 	      typename _MappingType::extents_type,
 	      typename _MappingType::layout_type, _AccessorType>;
 
+#if __glibcxx_submdspan
+  namespace __mdspan
+  {
+    template<typename _Slice>
+      consteval bool
+      __is_statically_empty_strided_slice()
+      {
+	if constexpr (__is_strided_slice<_Slice>)
+	  {
+	    using _ExtentType = typename _Slice::extent_type;
+	    return __detail::__integral_constant_like<_ExtentType>
+	      && _ExtentType() == 0;
+	  }
+	else
+	  return false;
+      }
+
+    template<typename _Slice>
+      consteval bool
+      __is_statically_sized_strided_slice()
+      {
+	if constexpr (__is_strided_slice<_Slice>)
+	  {
+	    using _ExtentType = typename _Slice::extent_type;
+	    using _StrideType = typename _Slice::stride_type;
+	    return __detail::__integral_constant_like<_ExtentType>
+	      && __detail::__integral_constant_like<_StrideType>;
+	  }
+	else
+	  return false;
+      }
+
+    template<typename _IndexType, typename _Slice>
+      consteval bool
+      __is_statically_sized_tuple()
+      {
+	if constexpr (__index_pair_like<_Slice, _IndexType>)
+	  return __detail::__integral_constant_like<tuple_element_t<0, _Slice>>
+	    && __detail::__integral_constant_like<tuple_element_t<1, _Slice>>;
+	else
+	  return false;
+      }
+
+
+    template<typename _IndexType, size_t _Extent, typename _Slice>
+      consteval size_t
+      __deduce_static_slice_extent()
+      {
+	if constexpr (is_convertible_v<_Slice, full_extent_t>)
+	  return _Extent;
+	else if constexpr (__is_statically_sized_tuple<_IndexType, _Slice>())
+	  return __unwrap_constant(tuple_element_t<1, _Slice>())
+	    - __unwrap_constant(tuple_element_t<0, _Slice>());
+	else if constexpr (__is_statically_empty_strided_slice<_Slice>())
+	  return 0;
+	else if constexpr (__is_statically_sized_strided_slice<_Slice>())
+	  return 1 + (__unwrap_constant(typename _Slice::extent_type()) - 1)
+	    / __unwrap_constant(typename _Slice::stride_type());
+	else
+	  return dynamic_extent;
+      }
+
+    template<size_t _K, typename _Extents, typename _Slice>
+      constexpr typename _Extents::index_type
+      __deduce_dynamic_slice_extent(const _Extents& __exts, _Slice __slice)
+      {
+	if constexpr (__is_strided_slice<_Slice>)
+	  return __slice.extent == 0 ? 0 :
+	    1 + (__unwrap_constant(__slice.extent) - 1)
+	      / __unwrap_constant(__slice.stride);
+	else
+	  return __slice_size<_K>(__exts, __slice);
+      }
+
+    template<typename _Slice, typename _IndexType>
+      concept __valid_slice_type = convertible_to<_Slice, _IndexType>
+	+ __index_pair_like<_Slice, _IndexType>
+	+ is_convertible_v<_Slice, full_extent_t>
+	+ __is_strided_slice<_Slice> == 1;
+
+    template<size_t _K, typename _Extents, typename _Slice>
+      constexpr bool
+      __is_valid_slice(const _Extents& __exts, const _Slice& __slice)
+      {
+	using _IndexType = typename _Extents::index_type;
+	if constexpr (__is_strided_slice<_Slice>)
+	  if (__slice.extent != 0 && __slice.stride == 0)
+	    return false;
+
+	auto __begin = __slice_begin<_IndexType>(__slice);
+	auto __end = __slice_end<_K>(__exts, __slice);
+	return std::cmp_less_equal(__begin, __end)
+	  && std::cmp_less_equal(__end, __exts.extent(_K));
+      }
+
+    template<typename _Extents, typename... _Slices>
+      constexpr bool
+      __all_valid_slices(const _Extents& __exts, const _Slices&... __slices)
+      {
+	auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>){
+	  return (__is_valid_slice<_Is>(__exts, __slices...[_Is]) && ...);
+	};
+	return __impl(make_index_sequence<sizeof...(_Slices)>());
+      }
+
+
+    template<typename _IndexType, size_t... _Extents, typename... _Slices>
+      requires (sizeof...(_Slices) == sizeof...(_Extents))
+      constexpr auto __submdspan_extents(
+	  const extents<_IndexType, _Extents...>& __exts, _Slices... __slices)
+      {
+	__glibcxx_assert(__mdspan::__all_valid_slices(__exts, __slices...));
+
+	constexpr auto __inv_map = __mdspan::__inv_map_rank<_IndexType,
+							    _Slices...>();
+	auto __impl = [&]<size_t... _Indices>(index_sequence<_Indices...>)
+	{
+	  using _SubExtents = extents<_IndexType,
+	      (__mdspan::__deduce_static_slice_extent<_IndexType,
+		 _Extents...[__inv_map[_Indices]],
+		 _Slices...[__inv_map[_Indices]]>())...>;
+	  if constexpr (_SubExtents::rank_dynamic() == 0)
+	    return _SubExtents{};
+	  else
+	    {
+	      using _StaticSubExtents = __mdspan::_StaticExtents<
+		__mdspan::__static_extents<_SubExtents>()>;
+	      auto __create = [&]<size_t... _Is>(index_sequence<_Is...>)
+	      {
+		constexpr auto __slice_idx = [__inv_map](size_t __i) consteval
+		{
+		  return __inv_map[_StaticSubExtents::_S_dynamic_index_inv(__i)];
+		};
+
+		return _SubExtents{
+		  (__mdspan::__deduce_dynamic_slice_extent<__slice_idx(_Is)>(
+		     __exts, __slices...[__slice_idx(_Is)]))...};
+	      };
+	      constexpr auto __dyn_subrank = _SubExtents::rank_dynamic();
+	      return __create(make_index_sequence<__dyn_subrank>());
+	    }
+	};
+
+	return __impl(make_index_sequence<__inv_map.size()>());
+      }
+  }
+
+  template<typename _IndexType, size_t... _Extents, typename... _Slices>
+    requires (sizeof...(_Slices) == sizeof...(_Extents))
+    constexpr auto submdspan_extents(
+	const extents<_IndexType, _Extents...>& __exts, _Slices... __slices)
+    {
+      static_assert((__mdspan::__valid_slice_type<_Slices, _IndexType> && ...));
+      return __mdspan::__submdspan_extents(__exts,
+	__mdspan::__slice_cast<_IndexType>(std::move(__slices))...);
+    }
+#endif
+
 _GLIBCXX_END_NAMESPACE_VERSION
 }
 #endif
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
index e9172c13455..67c759245f9 100644
--- a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
@@ -60,4 +60,13 @@ using RValueInt = CustomIndexType<CustomIndexKind::RValue>;
 struct NotIntLike
 { };
 
+struct StructuralInt
+{
+  constexpr
+  operator int() const noexcept
+  { return value; }
+
+  int value;
+};
+
 #endif // TEST_MDSPAN_INT_LIKE_H
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
new file mode 100644
index 00000000000..a20509a7fc6
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
@@ -0,0 +1,159 @@
+// { dg-do run { target c++26 } }
+#include <mdspan>
+#include <tuple>
+
+#include "../int_like.h"
+#include <testsuite_hooks.h>
+
+constexpr size_t dyn = std::dynamic_extent;
+constexpr auto all = std::full_extent;
+
+constexpr bool
+test_from_full_extent()
+{
+  auto exts = std::extents<int, 3, dyn, 7>{};
+  auto sub_exts = submdspan_extents(exts, 1, all, all);
+  VERIFY(sub_exts.rank() == 2);
+  VERIFY(sub_exts.static_extent(0) == dyn);
+  VERIFY(sub_exts.extent(0) == exts.extent(1));
+  VERIFY(std::cmp_equal(sub_exts.static_extent(1), exts.extent(2)));
+  return true;
+}
+
+template<template<int> typename Cw>
+  constexpr bool
+  test_from_tuple()
+  {
+    auto exts = std::extents<int, 3, 5, 7>{};
+    auto s0 = Cw<1>{};
+    auto s1 = std::tuple{Cw<1>{}, Cw<2>{}};
+    auto s2 = std::tuple{Cw<1>{}, 4};
+    auto sub_exts = submdspan_extents(exts, s0, s1, s2);
+    VERIFY(sub_exts.rank() == 2);
+    VERIFY(sub_exts.static_extent(0) == size_t(get<1>(s1) - get<0>(s1)));
+    VERIFY(sub_exts.static_extent(1) == dyn);
+    VERIFY(std::cmp_equal(sub_exts.extent(1), get<1>(s2) - get<0>(s2)));
+    return true;
+  }
+
+template<typename Int>
+  bool
+  test_from_int_like_as_scalar()
+  {
+    auto exts = std::extents<int, 3, 5>{};
+    auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{1, 3});
+    VERIFY(sub_exts.rank() == 1);
+    VERIFY(sub_exts.static_extent(0) == dyn);
+    VERIFY(sub_exts.extent(0) == 2);
+    return true;
+  }
+
+template<typename Int>
+  bool
+  test_from_const_int()
+  {
+    auto exts = std::extents<int, 3, 5>{};
+    auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{1, 3});
+    VERIFY(sub_exts.rank() == 1);
+    VERIFY(sub_exts.static_extent(0) == dyn);
+    VERIFY(sub_exts.extent(0) == 2);
+    return true;
+  }
+
+template<typename Int>
+  constexpr bool
+  test_from_int_like_in_tuple()
+  {
+    auto exts = std::extents<int, 3, 5>{};
+    auto sub_exts = submdspan_extents(exts, Int(1), std::tuple{Int(1), Int(3)});
+    VERIFY(sub_exts.rank() == 1);
+    VERIFY(sub_exts.static_extent(0) == dyn);
+    VERIFY(sub_exts.extent(0) == 2);
+    return true;
+  }
+
+template<template<int> typename Cw>
+  constexpr bool
+  test_from_strided_slice()
+  {
+    auto exts = std::extents<int, 5, 7, 11>{};
+    {
+      auto s0 = 1;
+      auto s1 = std::strided_slice{0, 0, 0};
+      auto s2 = std::strided_slice{1, Cw<0>{}, 0};
+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
+      VERIFY(sub_exts.rank() == 2);
+      VERIFY(sub_exts.static_extent(0) == dyn);
+      VERIFY(sub_exts.extent(0) == 0);
+      VERIFY(sub_exts.static_extent(1) == 0);
+    }
+
+    {
+      auto s0 = 1;
+      auto s1 = std::strided_slice{0, 2, Cw<1>{}};
+      auto s2 = std::strided_slice{1, Cw<2>{}, 1};
+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
+      VERIFY(sub_exts.rank() == 2);
+      VERIFY(sub_exts.static_extent(0) == dyn);
+      VERIFY(sub_exts.extent(0) == 2);
+      VERIFY(sub_exts.static_extent(1) == dyn);
+      VERIFY(sub_exts.extent(1) == 2);
+    }
+
+    {
+      // selected = 1 x [1, 3] x [1, 4, 7, 10]
+      auto s0 = 1;
+      auto s1 = std::strided_slice{1, Cw<4>{}, 2};
+      auto s2 = std::strided_slice{1, Cw<10>{}, Cw<3>{}};
+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
+      VERIFY(sub_exts.rank() == 2);
+      VERIFY(sub_exts.static_extent(0) == dyn);
+      VERIFY(sub_exts.extent(0) == 2);
+      VERIFY(sub_exts.static_extent(1) == 4);
+    }
+
+    {
+      // selected = [0, 2] x [1, 3] x [0, 3, 6]
+      auto s0 = std::strided_slice(0, 3, 2);
+      auto s1 = std::strided_slice(1, 4, 2);
+      auto s2 = std::strided_slice(0, 7, 3);
+      auto sub_exts = submdspan_extents(exts, s0, s1, s2);
+      VERIFY(sub_exts.rank() == 3);
+      VERIFY(sub_exts.extent(0) == 2);
+      VERIFY(sub_exts.extent(1) == 2);
+      VERIFY(sub_exts.extent(2) == 3);
+    }
+    return true;
+  }
+
+template<int Value>
+  using CW = std::constant_wrapper<Value, int>;
+
+template<int Value>
+  using IC = std::integral_constant<int, Value>;
+
+constexpr bool
+test_all()
+{
+  test_from_full_extent();
+  test_from_tuple<CW>();
+  test_from_tuple<IC>();
+  test_from_strided_slice<CW>();
+  test_from_strided_slice<IC>();
+  test_from_int_like_in_tuple<StructuralInt>();
+  return true;
+}
+
+int
+main()
+{
+  test_all();
+  static_assert(test_all());
+
+  test_from_int_like_as_scalar<IntLike>();
+  test_from_int_like_as_scalar<MutatingInt>();
+  test_from_int_like_as_scalar<ThrowingInt>();
+  test_from_int_like_as_scalar<RValueInt>();
+  test_from_int_like_as_scalar<StructuralInt>();
+  return 0;
+}
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
new file mode 100644
index 00000000000..dca294c185e
--- /dev/null
+++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
@@ -0,0 +1,66 @@
+// { dg-do compile { target c++26 } }
+#include <mdspan>
+
+#include <cstdint>
+
+struct NotASlice
+{ };
+
+struct AmbiguousSlice
+{
+  constexpr
+  operator int() const
+  { return 0; }
+
+  constexpr
+  operator std::full_extent_t() const
+  { return std::full_extent; }
+};
+
+constexpr bool
+test_unrelated_stride_type()
+{
+  auto exts = std::extents(3, 5, 7);
+  auto sub_exts = submdspan_extents(exts, 1, NotASlice{}, 2);  // { dg-error "required from" }
+  return true;
+}
+static_assert(test_unrelated_stride_type());
+
+constexpr bool
+test_ambiguous_stride_type()
+{
+  auto exts = std::extents(3, 5, 7);
+  auto sub_exts = submdspan_extents(exts, 1, AmbiguousSlice{}, 2);  // { dg-error "required from" }
+  return true;
+}
+static_assert(test_ambiguous_stride_type());
+
+constexpr bool
+test_invalid_stride_zero()
+{
+  auto exts = std::extents(3, 5, 7);
+  auto sub_exts = submdspan_extents(exts, 1, std::strided_slice{0, 1, 0}, 2);  // { dg-error "expansion of" }
+  return true;
+}
+static_assert(test_invalid_stride_zero());
+
+template<typename Slice>
+constexpr bool
+test_out_of_bounds_selection(const Slice& slice)
+{
+  auto exts = std::extents<uint16_t, 3, 5, 7>{};
+  auto sub_exts = submdspan_extents(exts, 1, slice, 2);  // { dg-error "expansion of" }
+  return true;
+}
+static_assert(test_out_of_bounds_selection(std::strided_slice{0, 6, 1}));  // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::strided_slice{0, 7, 2}));  // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::strided_slice{1, 6, 1}));  // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::strided_slice{1, 6, 2}));  // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::tuple{1, 6}));             // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::tuple{std::cw<1>, std::cw<6>})); // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::strided_slice{-1, 2, 1})); // { dg-error "expansion of" }
+static_assert(test_out_of_bounds_selection(std::tuple{-1, 2}));            // { dg-error "expansion of" }
+
+// { dg-prune-output "static assertion failed" }
+// { dg-prune-output "__glibcxx_assert_fail" }
+// { dg-prune-output "non-constant condition" }
-- 
2.50.1



More information about the Libstdc++ mailing list