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)

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>
+             && _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, 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...);
+      return __mdspan::__subextents(__exts, __slices...);
+    }
+
   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