On Tue, Nov 18, 2025 at 3:28 PM Luc Grosheintz <[email protected]>
wrote:

> Implement submdspan_extents as described in P3663 and adds it to the std
> module.
>
> There's one deviation from the standard. Doesn't (under all
> circumstances) require:
>
>   0 <= begin[k] <= end[k] <= exts.extent(k)
>
> where the k-th slice range is [begin[k], end[k]). Instead, it requires
> that the k-th slice ranges is contained in the k-th extent interval. If
> the the slice range is empty, then that condition is always satisfied,
> even if
>
>   begin[k] == end[k] > exts.extent(k)
>
This was always intended to work, so please submit the LWG issue.

>
> The deviation is that we enforce the above inequality through
> preconditions. This is analogous to what the standard requires if
> begin[k] is a constant wrapper.
>
>         PR libstdc++/110352
>
> libstdc++-v3/ChangeLog:
>
>         * include/std/mdspan (submdspan_extents): New function.
>         * src/c++23/std.cc.in: Add submdspan_extents.
>         * 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 <[email protected]>
> ---
>  libstdc++-v3/include/std/mdspan               | 135 ++++++++++++++
>  libstdc++-v3/src/c++23/std.cc.in              |   5 +-
>  .../testsuite/23_containers/mdspan/int_like.h |   9 +
>  .../mdspan/submdspan/submdspan_extents.cc     | 169 ++++++++++++++++++
>  .../mdspan/submdspan/submdspan_extents_neg.cc |  48 +++++
>  5 files changed, 365 insertions(+), 1 deletion(-)
>  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 70656bcdd01..36e04f7e1b5 100644
> --- a/libstdc++-v3/include/std/mdspan
> +++ b/libstdc++-v3/include/std/mdspan
> @@ -1003,20 +1003,44 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>        __assert_valid_slices(const _Extents& __exts, const _Slices&...
> __slices)
>        {
>         constexpr auto __rank = _Extents::rank();
>         auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>)
>         {
>           ((__assert_valid_slice(__extract_extent<_Is>(__exts),
>                                  __slices...[_Is])),...);
>         };
>         __impl(make_index_sequence<__rank>());
>        }
> +
> +    template<typename _IndexType, typename... _Slices>
> +      consteval auto
> +      __subrank()
> +      {
> +       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 = __subrank<_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;
> +      }
>  #endif // __glibcxx_submdspan
>    }
>
>    template<typename _Extents>
>      class layout_left::mapping
>      {
>      public:
>        using extents_type = _Extents;
>        using index_type = typename extents_type::index_type;
>        using size_type = typename extents_type::size_type;
> @@ -2678,20 +2702,131 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>               typename _MappingType::layout_type>;
>
>    template<typename _MappingType, typename _AccessorType>
>      mdspan(const typename _AccessorType::data_handle_type&, const
> _MappingType&,
>            const _AccessorType&)
>      -> mdspan<typename _AccessorType::element_type,
>               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>
>
We do not need __intergral_constant_like (and the expressions matching)
for concepts, is_constant_wrapper should be sufficient. This is why the
normalization is in place.

> +             && _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>;
>
Same here.

> +         }
> +       else
> +         return false;
> +      }
> +
> +    template<typename _IndexType, size_t _Extent, typename _Slice>
> +      consteval size_t
> +      __deduce_static_slice_extent()
> +      {
> +       if constexpr (same_as<_Slice, full_extent_t>)
> +         return _Extent;
> +       else if constexpr (__is_statically_empty_strided_slice<_Slice>())
>
+         return 0;
> +       else if constexpr (__is_statically_sized_strided_slice<_Slice>())
> +         return 1 + ((typename _Slice::extent_type{}) - 1)
> +                  / (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)
> +      {
> +       using _IndexType = typename _Extents::index_type;
> +       if constexpr (__is_strided_slice<_Slice>)
> +         return __slice.extent == 0 ? 0 :
> +           1 + (__slice.extent - 1) / __slice.stride;
> +       else if constexpr (convertible_to<_Slice, _IndexType>)
> +         return 1;
> +       else
> +         return __exts.extent(_K);
> +
> +      }
> +
> +    template<typename _IndexType, size_t... _Extents, typename... _Slices>
> +      requires (sizeof...(_Slices) == sizeof...(_Extents))
> +      constexpr auto
> +      __subextents(const extents<_IndexType, _Extents...>& __exts,
> +                  _Slices... __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... __raw_slices)
> +    {
> +      auto [...__slices] = submdspan_canonicalize_slices(__exts,
> __raw_slices...);
>
I would much more prefer if we would not instantiate tuple here, as this is
compile
heavy, and completely unnecessary do:
  return __mdspan::__subextents(__exts, slice_cast(__slices)...);
And insert __mdspan::__assert_valid_slices(__exts, __slices...) into
__subextents.
This will apply to any other calls to canonicalize_slices in later patches.

>
> +    }
> +
>    template<typename _IndexType, size_t... _Extents, typename... _Slices>
>      requires (sizeof...(_Extents) == sizeof...(_Slices))
>      constexpr auto
>      submdspan_canonicalize_slices(const extents<_IndexType, _Extents...>&
> __exts,
>                                   _Slices... __raw_slices)
>      {
>        auto [...__slices]
>         = make_tuple(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>        __mdspan::__assert_valid_slices(__exts, __slices...);
>        return make_tuple(__slices...);
> diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/
> std.cc.in
> index 63b3d183bf5..c2a9293b05a 100644
> --- a/libstdc++-v3/src/c++23/std.cc.in
> +++ b/libstdc++-v3/src/c++23/std.cc.in
> @@ -1870,27 +1870,30 @@ export namespace std
>    using std::layout_right;
>    using std::layout_stride;
>    using std::default_accessor;
>  #if __glibcxx_aligned_accessor
>    using std::aligned_accessor;
>  #endif
>    using std::mdspan;
>  #if __glibcxx_padded_layouts
>    using std::layout_left_padded;
>    using std::layout_right_padded;
> +#endif
> +#if __glibcxx_submdspan
>    using std::strided_slice;
>    using std::full_extent_t;
>    using std::full_extent;
>    using std::submdspan_mapping_result;
>    using std::submdspan_canonicalize_slices;
> +  using std::submdspan_extents;
>  #endif
> -  // FIXME submdspan_extents, mdsubspan
> +  // FIXME mdsubspan
>  }
>  #endif
>
>  // 20.2 <memory>
>  export namespace std
>  {
>    using std::align;
>    using std::allocator;
>    using std::allocator_arg;
>    using std::allocator_arg_t;
> diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
> b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
> index 2f79b9dd3b5..b839be73ae9 100644
> --- a/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/int_like.h
> @@ -62,11 +62,20 @@ template<CustomIndexKind Kind, bool Copyable = false>
>    };
>
>  using IntLike = CustomIndexType<CustomIndexKind::Const>;
>  using ThrowingInt = CustomIndexType<CustomIndexKind::Throwing>;
>  using MutatingInt = CustomIndexType<CustomIndexKind::Mutating>;
>  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..841910a77c8
> --- /dev/null
> +++
> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents.cc
> @@ -0,0 +1,169 @@
> +// { dg-do run { target c++26 } }
> +#include <mdspan>
> +
> +#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<typename, typename> typename Pair, template<int>
> typename Cw>
> +  constexpr bool
> +  test_from_tuple()
> +  {
> +    auto exts = std::extents<int, 3, 5, 7>{};
> +    auto s0 = Cw<1>{};
> +    auto s1 = Pair{Cw<1>{}, Cw<2>{}};
> +    auto s2 = Pair{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<template<int> typename Cw>
> +  constexpr bool
> +  test_from_tuple_all()
> +  {
> +    test_from_tuple<std::tuple, Cw>();
> +    test_from_tuple<std::pair, Cw>();
> +    return true;
> +  }
> +
> +
> +template<typename Int>
> +  void
> +  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);
> +  }
> +
> +template<template<int> typename Cw>
> +  constexpr bool
> +  test_from_const_int()
> +  {
> +    auto exts = std::extents<int, 3, 5>{};
> +    auto sub_exts = submdspan_extents(exts, Cw<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_all<CW>();
> +  test_from_tuple_all<IC>();
> +  test_from_const_int<CW>();
> +  test_from_const_int<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<CustomIndexType<CustomIndexKind::Const,
> true>>();
> +  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Throwing,
> true>>();
> +  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::Mutating,
> true>>();
> +  test_from_int_like_as_scalar<CustomIndexType<CustomIndexKind::RValue,
> true>>();
> +  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..cf27c0c7e4d
> --- /dev/null
> +++
> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_extents_neg.cc
> @@ -0,0 +1,48 @@
> +// { dg-do compile { target c++26 } }
> +#include <mdspan>
> +
> +#include <cstdint>
> +
> +struct NotASlice
> +{ };
> +
> +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_invalid_stride_zero()
> +{
> +  auto exts = std::extents(3, 5, 7);
> +  auto s = std::strided_slice{0, 1, 0};
> +  auto sub_exts = submdspan_extents(exts, 1, s, 2);  // { dg-error
> "expansion of" }
> +  return true;
> +}
> +static_assert(test_invalid_stride_zero());
> +
> +template<typename Slice>
> +constexpr bool
> +test_out_of_bounds(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(std::strided_slice{0, 6, 1}));  // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::strided_slice{0, 7, 2}));  // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::strided_slice{1, 6, 1}));  // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::strided_slice{1, 6, 2}));  // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::tuple{1, 6}));             // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::tuple{std::cw<1>, std::cw<6>})); //
> { dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::strided_slice{-1, 2, 1})); // {
> dg-error "expansion of" }
> +static_assert(test_out_of_bounds(std::tuple{-1, 2}));            // {
> dg-error "expansion of" }
> +
> +// { dg-prune-output "cannot decompose class type 'NotASlice'" }
> +// { dg-prune-output "static assertion failed" }
> +// { dg-prune-output "__glibcxx_assert_fail" }
> +// { dg-prune-output "non-constant condition" }
> --
> 2.51.2
>
>

Reply via email to