libstdc++: Implement submdspan_extents. [PR110352]

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.

Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Luc Grosheintz <luc.grosheintz@gmail.com>
This commit is contained in:
Luc Grosheintz
2025-12-02 15:40:59 +01:00
committed by Tomasz Kamiński
parent f6f9aec0c6
commit c915bfc0f7
5 changed files with 333 additions and 1 deletions
+103
View File
@@ -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
}
@@ -2664,8 +2689,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
+4 -1
View File
@@ -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
@@ -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
@@ -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;
}
@@ -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" }