On Tue, Dec 2, 2025 at 9:10 AM Tomasz Kaminski <[email protected]> wrote:

>
>
> On Mon, Dec 1, 2025 at 9:43 AM Luc Grosheintz <[email protected]>
> wrote:
>
>> Implement submdspan_extents as described in P3663 and adds it to the std
>> module.
>>
>>         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]>
>> ---
>>
> LGTM. Thanks.
>
I have double checked and all functions call are qualified in this patch.

>  libstdc++-v3/include/std/mdspan               | 103 +++++++++++
>>  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, 333 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 4fce6172589..31395b068b9 100644
>> --- a/libstdc++-v3/include/std/mdspan
>> +++ b/libstdc++-v3/include/std/mdspan
>> @@ -865,6 +865,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>         return extents<_IndexType, _Extents::static_extent(_Index)>{
>>           __exts.extent(_Index)};
>>        }
>> +
>> +    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<bool, __rank>{
>> +         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
>>    }
>>
>> @@ -2659,8 +2684,86 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION
>>         };
>>         __impl(make_index_sequence<__rank>());
>>        }
>> +
>> +    template<typename _IndexType, size_t _Extent, typename _Slice>
>> +      consteval size_t
>> +      __static_slice_extent()
>> +      {
>> +       if constexpr (same_as<_Slice, full_extent_t>)
>> +         return _Extent;
>> +       else if constexpr (same_as<_Slice,
>> constant_wrapper<_IndexType(0)>>)
>> +         return 0;
>> +       else if constexpr (__is_constant_wrapper<typename
>> _Slice::extent_type>
>> +                       && __is_constant_wrapper<typename
>> _Slice::stride_type>)
>> +         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
>> +      __dynamic_slice_extent(const _Extents& __exts, _Slice __slice)
>> +      {
>> +       if constexpr (__is_strided_slice<_Slice>)
>> +         return __slice.extent == 0 ? 0 : 1 + (__slice.extent - 1) /
>> __slice.stride;
>> +       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::__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::__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...
>> _RawSlices>
>> +    requires (sizeof...(_RawSlices) == sizeof...(_Extents))
>> +    constexpr auto
>> +    submdspan_extents(const extents<_IndexType, _Extents...>& __exts,
>> +                     _RawSlices... __raw_slices)
>> +    {
>> +      auto __impl = [&__exts](auto... __slices)
>> +      {
>> +       __mdspan::__check_valid_slices(__exts, __slices...);
>> +       return __mdspan::__subextents(__exts, __slices...);
>> +      };
>> +      return __impl(__mdspan::__slice_cast<_IndexType>(__raw_slices)...);
>> +    }
>> +
>>    template<typename _IndexType, size_t... _Extents, typename...
>> _RawSlices>
>>      requires (sizeof...(_Extents) == sizeof...(_RawSlices))
>>      constexpr auto
>> 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
>> @@ -1877,13 +1877,16 @@ export namespace std
>>  #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
>>
>> 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
>> @@ -69,4 +69,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..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.52.0
>>
>>

Reply via email to