On Mon, Dec 1, 2025 at 3:25 PM Luc Grosheintz <[email protected]> wrote:
> > > On 11/20/25 17:08, Tomasz Kaminski wrote: > > On Tue, Nov 18, 2025 at 3:37 PM Luc Grosheintz <[email protected] > > > > wrote: > > > >> Implements `submdspan` and `submdspan_mapping` for layout_left as > >> described in P3663 (Future proofing mdspan). > >> > >> When computing the offset of the submdspan, one must check that the > >> lower bound of the slice range isn't out-of-range. There's a few > >> cases when the lower bound is never out-of-range: > >> > >> - full_extent and exts.extent(k) != 0, > >> - collapsing slice types. > >> > >> If those conditions are known to hold, no checks are generated. > >> > > > >> Similarly, if all slices are full_extent, there's no need to call > >> mapping(0,...,0) for standardized mappings. > >> > >> The implementation prepares to use the symmetry between layout_left and > >> layout_right and introduces concepts like a "layout side", i.e. left, > >> right or unknown/strided. > >> > >> The tests use an iterator to replace nested for-loops. Which also makes > >> it easier to write the core test logic in a rank-independent manner. > >> > >> PR libstdc++/110352 > >> > >> libstdc++-v3/ChangeLog: > >> > >> * include/std/mdspan (layout_left::mapping::submdspan_mapping): > >> New friend function. > >> (submdspan): New function. > >> * src/c++23/std.cc.in: Add submdspan. > >> * testsuite/23_containers/mdspan/submdspan/submdspan.cc: New > test. > >> * > testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc: > >> New test. > >> * testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc: > New > >> test. > >> > >> Signed-off-by: Luc Grosheintz <[email protected]> > >> > > Posted some idea on how to classify the slices without so much > > metaprogramming, > > the idea would be to have function, that would have constexpr array of > > slice kind, > > would go over it either left to right or right to left, and return if we > > have matching > > blocks. Could accept rank as a parameter. We most likely want this > function > > to simply > > accept span, subrank, and direction, and not be templated. > > > > Could you let me know what you think about the idea? > > I think I will stop review here, as this will affect the rest of the > code. > > I have a version drafted up: it works reasonably well, only I'd rather > reverse the array of SliceKind than pass in a direction. It feels easier > to express: same as left; but from the back. Makes it explicit that there > is no difference between left and right. Since, it's all consteval, there's > no cost at runtime; and at compile time we save instantiating once for > each direction. > > --------------- > > Should all standardized mapping have `_M_strides`? I don't object, but > the usage is slightly different: _M_strides creates an array of length > `rank`; while the loop in question only needs an array of length > `subrank`, it only needs the strides at non-collapsing slices. > > --------------- > > About qualified calls (to avoid ADL). Is this also required for uglified > names, e.g. __subextents? Since users aren't permitted to use those names > they can't cause ADL-related issues. > The ADL related issues are not limited to calling the user-defined function, if ADL is performed for the class that is incomplete, that will cause problems. For example: struct Incomplete; std::unique_ptr<Incomplete> u1, u2; using std::swap(); swap(u1, u2); // ADL also searches for associated namespace of template arguments, i.e. Incomplete here. > If we know that the types of all arguments are "ours", i.e. defined in > std:: or builtins like `int`, do we still need to protect against ADL? > After canonicalization, we're left with: standarsized mapping, extents, > full_extent_t, strided_slice, constant_wrapper and integers. > Users are not allowed to add declarations to std, so yes the set would be limited, but we may still have internal ADL-related problems (picking deleted functions, e.t.c). > > Sofar I've been implementing everything as if the answer were: "no" to > both questions. If that's wrong, let me know, and I'll do a pass to > clean up all of <mdspan>. > I think it would be safer, and it will also improve the compile time (with qualified name compilers can just skip ADL) if we qualify all names. > > > > > > >> --- > >> libstdc++-v3/include/std/mdspan | 387 ++++++++++++++++++ > >> libstdc++-v3/src/c++23/std.cc.in | 2 +- > >> .../mdspan/submdspan/submdspan.cc | 369 +++++++++++++++++ > >> .../mdspan/submdspan/submdspan_mapping.cc | 136 ++++++ > >> .../mdspan/submdspan/submdspan_neg.cc | 102 +++++ > >> 5 files changed, 995 insertions(+), 1 deletion(-) > >> create mode 100644 > >> libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc > >> create mode 100644 > >> > libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc > >> create mode 100644 > >> libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc > >> > >> diff --git a/libstdc++-v3/include/std/mdspan > >> b/libstdc++-v3/include/std/mdspan > >> index 36e04f7e1b5..712826ea7e7 100644 > >> --- a/libstdc++-v3/include/std/mdspan > >> +++ b/libstdc++-v3/include/std/mdspan > >> @@ -578,20 +578,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >> return __r == 0 ? 1 : __exts.extent(0); > >> else if constexpr > >> (__all_dynamic(std::span(__sta_exts).first(__rank-1))) > >> return __extents_prod(__exts, 1, 0, __r); > >> else > >> { > >> size_t __sta_prod = __fwd_partial_prods<__sta_exts>[__r]; > >> return __extents_prod(__exts, __sta_prod, 0, __r); > >> } > >> } > >> > >> + template<typename _IndexType, size_t _Nm> > >> + consteval _IndexType > >> + __fwd_prod(span<const _IndexType, _Nm> __values) > >> + { > >> + _IndexType __ret = 1; > >> + for(auto __value : __values) > >> + __ret *= __value; > >> + return __ret; > >> + } > >> + > >> // Preconditions: _r < _Extents::rank() > >> template<typename _Extents> > >> constexpr typename _Extents::index_type > >> __rev_prod(const _Extents& __exts, size_t __r) noexcept > >> { > >> constexpr size_t __rank = _Extents::rank(); > >> constexpr auto& __sta_exts = __static_extents<_Extents>(); > >> if constexpr (__rank == 1) > >> return 1; > >> else if constexpr (__rank == 2) > >> @@ -1027,20 +1037,374 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >> 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; > >> } > >> + > >> + template<typename _IndexType, typename _Slice> > >> + constexpr _IndexType > >> + __slice_begin(_Slice __slice) > >> + { > >> > > Please consider implementing this as: > > if constexpr (is_same_v<_Slice, _full_extent_t>) > > return 0; > > else if constexpr (__is_strided_slice<_Slice>) > > return __slice.offset; > > else > > return __slice; > > This should be lighter to check that using concept, > > and you do not need to pass _IndexType. > > > > > > + if constexpr (convertible_to<_Slice, _IndexType>) > >> + return __slice; > >> + else if constexpr (__is_strided_slice<_Slice>) > >> + return __slice.offset; > >> + else > >> + return 0; // full_extent > >> + } > >> + > >> + template<typename _Mapping, typename... _Slices> > >> + constexpr size_t > >> + __suboffset(const _Mapping& __mapping, const _Slices&... > __slices) > >> + { > >> + using _IndexType = typename _Mapping::index_type; > >> + auto __any_past_the_end = [&]<size_t... > >> _Is>(index_sequence<_Is...>) > >> + { > > > > + auto __is_past_the_end = [](const auto& __slice, const auto& > >> __ext) > >> + { > >> + using _Slice = remove_cvref_t<decltype(__slice)>; > >> > > if constexpr (__ext.static_extent( > > > > > > I will suggest expressing this as: > > if constexpr (is_same_v<_Slice, __full_extent_t>) > > return true; // we checked that before > > else if constexpr (!_is_strided_slice<_Slice>) > > return false; > > else > > // Compiler wii probably optimize if this two are constant. > > return __slice.offset = __ext.extent(0); > > > > > >> + if constexpr (is_convertible_v<_Slice, _IndexType>) > >> > > I do not think this is correct, we single indices to operators. > > > >> + return false; > >> + else if (same_as<_Slice, full_extent_t> > >> + && __ext.static_extent(0) > 0 > >> + && __ext.static_extent(0) != dynamic_extent) > >> > > > > + return false; > >> + else > >> + return __slice_begin<_IndexType>(__slice) == > __ext.extent(0); > >> > > We seem to this for __full_extent, before the previous if is not > constexpr. > > > >> + }; > >> + > >> + const auto& __exts = __mapping.extents(); > >> > > + return ((__is_past_the_end(__slices...[_Is], > >> + __extract_extent<_Is>(__exts))) || > >> ...); > >> + }; > >> + > >> + if constexpr ((same_as<_Slices, full_extent_t> && ...)) > >> + return __offset(__mapping); > >> + > >> > > I think I would check empty mdspan separately here, so if > > empty(__mapping.extents()) > > the return __mapping.required_span_size(), this will also simply > > __any_past_the_end. > > > > > >> + if constexpr (!((convertible_to<_Slices, _IndexType>) && ...)) > > > > + if > >> (__any_past_the_end(make_index_sequence<sizeof...(__slices)>())) > >> + return __mapping.required_span_size(); > >> + return __mapping(__slice_begin<_IndexType>(__slices)...); > >> > > Because we do not pass _IndexType to slice begin, use > > static_cast<_IndexType> here. > > > >> + } > >> + > >> + enum class _LayoutSide > >> + { > >> + __left, > >> + __right, > >> + __unknown > >> + }; > >> + > >> + template<typename _Mapping> > >> + consteval _LayoutSide > >> + __deduce_mapping_side() > >> + { > >> + if constexpr (__is_left_padded_mapping<_Mapping> > >> + || __mapping_of<layout_left, _Mapping>) > >> + return _LayoutSide::__left; > >> + if constexpr (__is_right_padded_mapping<_Mapping> > >> + || __mapping_of<layout_right, _Mapping>) > >> + return _LayoutSide::__right; > >> + else > >> + return _LayoutSide::__unknown; > >> + } > >> + > >> + template<_LayoutSide _Side, size_t _Rank> > >> + struct _StridesTrait > >> + { > >> + static constexpr const _LayoutSide _S_side = _Side; > >> + > >> + static constexpr size_t > >> + _S_idx(size_t __k) noexcept > >> + { > >> + if constexpr (_Side == _LayoutSide::__left) > >> + return __k; > >> + else > >> + return _Rank - 1 - __k; > >> + } > >> + > >> + template<typename _Mapping> > >> + static constexpr typename _Mapping::index_type > >> + _S_extent(const _Mapping& __mapping, size_t __k) > >> + { > >> + if (__k == 0) > >> + return __mapping.stride(_S_idx(1)); > >> > > Nice trick of handling padded and not padded, but comment on it. > > > >> + else > >> + return __mapping.extents().extent(_S_idx(__k)); > >> + } > >> + > >> + template<typename _IndexType, typename... _Slices> > >> + static consteval auto > >> + _S_inv_map() > >> + { > >> + static_assert(_Side != _LayoutSide::__unknown); > >> + auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>) > >> + { > >> + return __inv_map_rank<_IndexType, > >> _Slices...[_S_idx(_Is)]...>(); > >> + }; > >> + return __impl(make_index_sequence<_Rank>()); > >> + } > >> + }; > >> + > >> + template<typename _SubExtents, typename _Mapping, typename... > _Slices> > >> + constexpr auto > >> + __substrides_generic(const _Mapping& __mapping, const _Slices&... > >> __slices) > >> + { > >> + using _IndexType = typename _Mapping::index_type; > >> + if constexpr (_SubExtents::rank() == 0) > >> + return array<_IndexType, _SubExtents::rank()>{}; > >> + else > >> + { > >> + auto __stride = [&__mapping](size_t __k, auto __slice) -> > >> _IndexType > >> + { > >> + if constexpr (__is_strided_slice<decltype(__slice)>) > >> + if (__slice.stride < __slice.extent) > >> + return __mapping.stride(__k) * __slice.stride; > >> + return __mapping.stride(__k); > >> + }; > >> + > >> + auto __impl = [&]<size_t... _Is>(index_sequence<_Is...>) > >> + { > >> + constexpr auto __inv_map = __inv_map_rank<_IndexType, > >> _Slices...>(); > >> + return array<_IndexType, _SubExtents::rank()>{ > >> + __stride(__inv_map[_Is], > __slices...[__inv_map[_Is]])...}; > >> + }; > >> + return __impl(make_index_sequence<_SubExtents::rank()>()); > >> + } > >> + }; > >> + > >> + template<typename _SubExtents, typename _Mapping, typename... > _Slices> > >> + constexpr auto > >> + __substrides_standardized(const _Mapping& __mapping, > >> + const _Slices&... __slices) > >> + { > >> + using _IndexType = typename _Mapping::index_type; > >> + using _Trait = _StridesTrait<__deduce_mapping_side<_Mapping>(), > >> + _Mapping::extents_type::rank()>; > >> + using _SubTrait = > _StridesTrait<__deduce_mapping_side<_Mapping>(), > >> + _SubExtents::rank()>; > >> + > >> + constexpr size_t __sub_rank = _SubExtents::rank(); > >> + > >> + array<_IndexType, __sub_rank> __ret; > >> + if constexpr (__sub_rank > 0) > >> + { > >> + constexpr auto __inv_map > >> + = _Trait::template _S_inv_map<_IndexType, _Slices...>(); > >> + auto __loop = [&]<size_t... _Ks>(index_sequence<_Ks...>) > >> + { > >> + size_t __i0 = 0; > >> + size_t __stride = 1; > >> + auto __body = [&](size_t __k, auto __slice) > >> + { > >> + for (size_t __i = __i0; __i < __inv_map[__k]; ++__i) > >> + __stride *= _Trait::_S_extent(__mapping, __i); > > > > I wonder if for all standardized mappings, we should not add _M_strides > > (public) function to access that. This would be useful. > > > >> + > >> + size_t __krev = _SubTrait::_S_idx(__k); > >> + if constexpr (__is_strided_slice<decltype(__slice)>) > >> + __ret[__krev] = __stride * __slice.stride; > >> + else > >> + __ret[__krev] = __stride; > >> + > >> + __i0 = __inv_map[__k]; > >> + }; > >> + > >> + ((__body(_Ks, > >> __slices...[_Trait::_S_idx(__inv_map[_Ks])])),...); > >> + }; > >> + __loop(make_index_sequence<__sub_rank>()); > >> + } > >> + return __ret; > >> + } > >> + > >> + > >> + template<typename _SubExtents, typename _Mapping, typename... > _Slices> > >> + constexpr auto > >> + __substrides(const _Mapping& __mapping, const _Slices&... > __slices) > >> + { > >> + if constexpr (__deduce_mapping_side<_Mapping>() == > >> _LayoutSide::__unknown) > >> + return __substrides_generic<_SubExtents>(__mapping, > __slices...); > >> + else > >> + return __substrides_standardized<_SubExtents>(__mapping, > >> __slices...); > >> + } > >> + > >> + template<typename _Slice, typename _IndexType> > >> + concept __is_unit_stride_slice = > > > > > > (__is_strided_slice<_Slice> > >> + && __detail::__integral_constant_like<typename > >> _Slice::stride_type> > >> > > Usig is_constant_wrapper instead of __integral_constant_like. > > > >> + && _Slice::stride_type::value == 1) > >> + || same_as<_Slice, full_extent_t>; > >> + > >> + // _BlockSize - 1 > >> + // [full, ..., full, unit_slice , ...] > >> + template<typename _IndexType, size_t _BlockSize, typename... > _Slices> > >> > > I think this funciton would read much better if we do normal template > > parameter recursion, > > i.e. have typename _Slice, typename... _RemSlices, we should never > search a > > block > > with one element. > > > >> + consteval bool > >> + __is_block() > >> + { > >> + if constexpr (_BlockSize == 0 || _BlockSize > > sizeof...(_Slices)) > >> + return false; > >> + else if constexpr (_BlockSize == 1) > >> + return __is_unit_stride_slice<_Slices...[0], _IndexType>; > >> + else if constexpr (same_as<_Slices...[0], full_extent_t>) > >> > > This will be much simpler. > > > >> + { > >> + auto __recurse = []<size_t... _Is>(index_sequence<_Is...>) > >> + { > >> + return __is_block<_IndexType, _BlockSize - 1, > >> + _Slices...[_Is + 1]...>(); > >> + }; > >> + return __recurse(make_index_sequence<sizeof...(_Slices) - > >> 1>()); > >> + } > >> + else > >> + return false; > >> > > + } > >> + > >> + // __u __u + _BlockSize - 1 > >> + // [*, full, ..., full, unit_slice, *] > >> + template<typename _IndexType, size_t _Start, size_t _BlockSize, > >> + typename... _Slices> > >> + consteval size_t > >> + __find_block() > >> > > Hmm, I think that this function could be implemented much easier by > having: > > bool full_map[sizeof...(Slices)]{ is_same_v<full_exent_t, > Slices>.... > > }; > > Or even better having an array of classification of slices at the > > begining of the functo > > constexpr SliceKind kinds[sizeof...(Slices)]{ function to > classify, > > full_extent -> full, strided -> stride, unit }; > > And then pass it to classification functions. That will go over what > could > > be returned. > > > > > >> + { > >> + static_assert(_BlockSize != dynamic_extent, > >> + "The implementation can't handle submdspans with rank == > >> size_t(-1)"); > >> + > >> + if constexpr (sizeof...(_Slices) == 0) > >> + return dynamic_extent; > >> + else if constexpr (__is_block<_IndexType, _BlockSize, > >> _Slices...>()) > >> + return _Start; > >> + else > >> + { > >> + auto __recurse = []<size_t... _Is>(index_sequence<_Is...>) > >> + { > >> + return __find_block<_IndexType, _Start + 1, _BlockSize, > >> + _Slices...[_Is + 1]...>(); > >> + }; > >> + return __recurse(make_index_sequence<sizeof...(_Slices) - > >> 1>()); > >> + } > >> + } > >> + > >> + template<typename _IndexType, size_t _SubRank, typename... _Slices> > >> + static consteval bool > >> + __is_compact_block() > >> + { > >> + if constexpr (_SubRank == 0) > >> + return false; > >> + else > >> + return __find_block<_IndexType, 0, _SubRank, _Slices...>() > == 0; > >> + } > >> + > >> + // __u > >> + // [unit_slice, i, ..., k, full, ..., full, unit_slice, *] > >> + template<typename _IndexType, size_t _SubRank, typename _Slice, > >> + typename... _Slices> > >> + static consteval size_t > >> + __padded_block_begin_generic() > >> + { > >> + if constexpr (!__mdspan::__is_unit_stride_slice<_Slice, > >> _IndexType>) > >> + return dynamic_extent; > >> + else if constexpr (sizeof...(_Slices) == 0) > >> + return dynamic_extent; > >> + else > >> + { > >> + constexpr auto __u = __find_block<_IndexType, 0, _SubRank - > 1, > >> + _Slices...>(); > >> + if constexpr (__u != dynamic_extent) > >> + return __u + 1; > >> + else > >> + return dynamic_extent; > >> + } > >> + } > >> + > >> + template<_LayoutSide _Side, typename _IndexType, size_t _SubRank, > >> + typename... _Slices> > >> + static consteval size_t > >> + __padded_block_begin() > >> + { > >> + if constexpr (_Side == _LayoutSide::__left) > >> + return __padded_block_begin_generic<_IndexType, _SubRank, > >> + _Slices...>(); > >> + } > >> + > >> + template<_LayoutSide _Side> > >> + struct _SubMdspanMapping; > >> + > >> + template<> > >> + struct _SubMdspanMapping<_LayoutSide::__left> > >> + { > >> + using _Layout = layout_left; > >> + template<size_t _Pad> using _PaddedLayout = > >> layout_left_padded<_Pad>; > >> + > >> + template<typename _Mapping, size_t _Us> > >> + static consteval size_t > >> + _S_pad() > >> + { > >> + using _Extents = typename _Mapping::extents_type; > >> + constexpr auto __sta_exts = __static_extents<_Extents>(0, > _Us); > >> + if constexpr (!__all_static(__sta_exts)) > >> + return dynamic_extent; > >> + else > >> + return __fwd_prod(__sta_exts); > >> + } > >> + > >> + template<typename _IndexType, size_t _SubRank, typename... > _Slices> > >> + static consteval bool > >> + _S_is_unpadded_submdspan() > >> + { return __is_compact_block<_IndexType, _SubRank, > _Slices...>(); > >> } > >> + }; > >> + > >> + template<typename _Mapping> > >> + constexpr auto > >> + __submdspan_mapping_impl(const _Mapping& __mapping) > >> + { return submdspan_mapping_result{__mapping, 0}; } > >> + > >> + template<typename _Mapping, typename... _Slices> > >> + requires (sizeof...(_Slices) > 0) > >> + constexpr auto > >> + __submdspan_mapping_impl(const _Mapping& __mapping, _Slices... > >> __slices) > >> + { > >> + using _IndexType= typename _Mapping::index_type; > >> + constexpr auto __side = __deduce_mapping_side<_Mapping>(); > >> + using _Trait = _SubMdspanMapping<__side>; > >> + > >> + auto __offset = __suboffset(__mapping, __slices...); > >> > > The calls need to be qualified (and one above) and everywhere in general. > > + auto __sub_exts = submdspan_extents(__mapping. > > We should call __mdspan::__subextents as we already canonicalized > > slices. > > > >> + using _SubExtents = decltype(__sub_exts); > >> + constexpr auto __sub_rank = _SubExtents::rank(); > >> + if constexpr (_SubExtents::rank() == 0) > >> + return submdspan_mapping_result{ > >> + typename _Trait::_Layout::mapping(__sub_exts), __offset}; > >> + else if constexpr ( > >> + _Trait::template _S_is_unpadded_submdspan<_IndexType, > >> __sub_rank, > >> + _Slices...>()) > >> + return submdspan_mapping_result{ > >> + typename _Trait::_Layout::mapping(__sub_exts), __offset}; > >> + else if constexpr ( > >> + constexpr auto __u = __padded_block_begin<__side, > _IndexType, > >> + __sub_rank, > >> _Slices...>(); > >> + __u != dynamic_extent) > >> + { > >> + constexpr auto __pad = _Trait::template _S_pad<_Mapping, > >> __u>(); > >> + using _Layout = typename _Trait::template > _PaddedLayout<__pad>; > >> + return submdspan_mapping_result{ > >> + typename _Layout::mapping(__sub_exts, > __mapping.stride(__u)), > >> + __offset}; > >> + } > >> + else > >> + { > >> + auto __sub_strides > >> + = __substrides<_SubExtents>(__mapping, __slices...); > >> + return submdspan_mapping_result{ > >> + layout_stride::mapping(__sub_exts, __sub_strides), > >> __offset}; > >> + } > >> + } > >> #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; > >> @@ -1168,20 +1532,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >> template<typename _OExtents> > >> constexpr explicit > >> mapping(const _OExtents& __oexts, __mdspan::__internal_ctor) > >> noexcept > >> : _M_extents(__oexts) > >> { > >> static_assert(__mdspan::__representable_size<_OExtents, > >> index_type>, > >> "The size of OtherExtents must be representable as > >> index_type"); > >> > >> __glibcxx_assert(__mdspan::__is_representable_extents(_M_extents)); > >> } > >> > >> +#if __glibcxx_submdspan > >> + template<__mdspan::__valid_canonical_slice_type<index_type>... > >> _Slices> > >> + requires (extents_type::rank() == sizeof...(_Slices)) > >> + friend constexpr auto > >> + submdspan_mapping(const mapping& __mapping, _Slices... __slices) > >> + { return __mdspan::__submdspan_mapping_impl(__mapping, > >> __slices...); } > >> +#endif // __glibcxx_submdspan > >> + > >> [[no_unique_address]] extents_type _M_extents{}; > >> }; > >> > >> namespace __mdspan > >> { > >> template<typename _Extents, typename... _Indices> > >> constexpr typename _Extents::index_type > >> __linear_index_right(const _Extents& __exts, _Indices... > __indices) > >> noexcept > >> { > >> @@ -2824,16 +3196,31 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > >> 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...); > >> } > >> + > >> + template<typename _ElementType, typename _Extents, typename _Layout, > >> + typename _Accessor, typename... _Slices> > >> + requires (sizeof...(_Slices) == _Extents::rank()) > >> + constexpr auto > >> + submdspan( > >> + const mdspan<_ElementType, _Extents, _Layout, _Accessor>& __md, > >> + _Slices... __raw_slices) > >> + { > >> + auto [...__slices] = > submdspan_canonicalize_slices(__md.extents(), > >> + > __raw_slices...); > >> > > Again, we do not want to instantiate tuples, so _would do __impl lambda > > trick here, > > were we pass canonicalized slices as arguments. > > > >> + auto __result = submdspan_mapping(__md.mapping(), __slices...); > >> + return mdspan(__md.accessor().offset(__md.data_handle(), > >> __result.offset), > >> + __result.mapping, typename > >> _Accessor::offset_policy(__md.accessor())); > >> + } > >> #endif // __glibcxx_submdspan > >> > >> _GLIBCXX_END_NAMESPACE_VERSION > >> } > >> #endif > >> #endif > >> diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ > >> std.cc.in > >> index c2a9293b05a..2dac6a6d887 100644 > >> --- a/libstdc++-v3/src/c++23/std.cc.in > >> +++ b/libstdc++-v3/src/c++23/std.cc.in > >> @@ -1878,22 +1878,22 @@ export namespace std > >> 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; > >> + using std::submdspan; > >> #endif > >> - // 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/submdspan/submdspan.cc > >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc > >> new file mode 100644 > >> index 00000000000..53e91407a9c > >> --- /dev/null > >> +++ b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan.cc > >> @@ -0,0 +1,369 @@ > >> +// { dg-do run { target c++26 } } > >> +#include <mdspan> > >> + > >> +#include <iostream> // TODO remove > >> +#include <vector> > >> +#include <numeric> > >> +#include "../layout_traits.h" > >> +#include <testsuite_hooks.h> > >> + > >> +constexpr size_t dyn = std::dynamic_extent; > >> +constexpr auto all = std::full_extent; > >> + > >> +template<typename T> > >> + constexpr bool is_strided_slice = false; > >> + > >> +template<typename O, typename E, typename S> > >> + constexpr bool is_strided_slice<std::strided_slice<O, E, S>> = true; > >> + > >> +template<typename MDSpan> > >> + constexpr void > >> + fill(const MDSpan& md) > >> + { > >> + using IndexType = typename MDSpan::index_type; > >> + auto exts = md.extents(); > >> + if constexpr (exts.rank() == 3) > >> + for(IndexType i = 0; i < exts.extent(0); ++i) > >> + for(IndexType j = 0; j < exts.extent(1); ++j) > >> + for(IndexType k = 0; k < exts.extent(2); ++k) > >> + md[i, j, k] = 100 * i + 10 * j + k; > >> + } > >> + > >> +template<typename Int, size_t Rank> > >> + class multi_index_generator > >> + { > >> + class EndIt > >> + { }; > >> + > >> + class BeginIt > >> + { > >> + public: > >> + constexpr > >> + BeginIt(const std::array<Int, Rank>& shape) > >> + : M_shape(shape) > >> + { } > >> + > >> + constexpr BeginIt& > >> + operator++() > >> + { > >> + if constexpr (Rank > 0) > >> + { > >> + ++M_indices[Rank-1]; > >> + for(size_t i = Rank; i > 1; --i) > >> + if (M_indices[i-1] == M_shape[i-1]) > >> + { > >> + M_indices[i-1] = 0; > >> + ++M_indices[i-2]; > >> + } > >> + } > >> + return *this; > >> + } > >> + > >> + constexpr auto > >> + operator*() > >> + { return M_indices; } > >> + > >> + constexpr bool > >> + operator==(EndIt) > >> + { > >> + if constexpr (Rank > 0) > >> + return M_indices[0] == M_shape[0]; > >> + else > >> + return true; > >> + } > >> + > >> + private: > >> + std::array<Int, Rank> M_indices{}; > >> + std::array<Int, Rank> M_shape; > >> + }; > >> + > >> + public: > >> + constexpr > >> + multi_index_generator(std::array<Int, Rank> shape) > >> + : M_shape(shape) > >> + { } > >> + > >> + constexpr BeginIt > >> + begin() const > >> + { return BeginIt(M_shape); } > >> + > >> + constexpr EndIt > >> + end() const > >> + { return EndIt{}; } > >> + > >> + private: > >> + std::array<Int, Rank> M_shape; > >> + }; > >> + > >> +constexpr bool > >> +test_multi_index() > >> +{ > >> + auto shape = std::array{3, 5, 7, 1}; > >> + > >> + std::vector<std::array<int, 4>> expected; > >> + for (int i = 0; i < shape[0]; ++i) > >> + for (int j = 0; j < shape[1]; ++j) > >> + for (int k = 0; k <shape[2]; ++k) > >> + for (int l = 0; l <shape[3]; ++l) > >> + expected.push_back(std::array{i, j, k, l}); > >> + > >> + size_t i = 0; > >> + for (auto actual : multi_index_generator(shape)) > >> + VERIFY(expected[i++] == actual); > >> + return true; > >> +} > >> + > >> +static_assert(test_multi_index()); > >> + > >> +struct > >> +collapse > >> +{ }; > >> + > >> +template<typename... Slices> > >> + consteval auto > >> + inv_collapsed_index_map() > >> + { > >> + constexpr size_t rank = sizeof...(Slices); > >> + auto is_collapsing = std::array{std::same_as<Slices, collapse>...}; > >> + constexpr auto collapsed_rank = ((!std::same_as<Slices, collapse>) > + > >> ... + 0); > >> + > >> + std::array<size_t, collapsed_rank> ret; > >> + if constexpr (collapsed_rank > 0) > >> + for(size_t k = 0, i = 0; i < rank; ++i) > >> + if (!is_collapsing[i]) > >> + ret[k++] = i; > >> + return ret; > >> + } > >> + > >> +static_assert(inv_collapsed_index_map<collapse, collapse, collapse>() > >> + == std::array<size_t, 0>{}); > >> + > >> +static_assert(inv_collapsed_index_map<collapse, decltype(all), > collapse>() > >> + == std::array<size_t, 1>{1}); > >> + > >> +template<typename IndexType, typename Slice> > >> + constexpr std::vector<IndexType> > >> + make_selection(IndexType extent, const Slice& slice) > >> + { > >> + if constexpr (std::convertible_to<Slice, IndexType>) > >> + return {static_cast<IndexType>(slice)}; > >> + else if constexpr (std::same_as<Slice, std::full_extent_t>) > >> + { > >> + auto ret = std::vector<IndexType>(static_cast<size_t>(extent)); > >> + std::ranges::iota(ret, 0); > >> + return ret; > >> + } > >> + else if constexpr (is_strided_slice<Slice>) > >> + { > >> + auto ret = std::vector<IndexType>{}; > >> + size_t n = static_cast<size_t>(slice.extent); > >> + for(size_t i = 0; i < n; i += slice.stride) > >> + ret.push_back(slice.offset + i); > >> + return ret; > >> + } > >> + else > >> + { > >> + auto [begin, end] = slice; > >> + auto ret = std::vector<IndexType>(static_cast<size_t>(end - > >> begin)); > >> + std::ranges::iota(ret, begin); > >> + return ret; > >> + } > >> + } > >> + > >> +template<typename Layout, size_t... I, typename... Slices> > >> + constexpr bool > >> + check_selection(std::index_sequence<I...>, auto md, Slices... slices) > >> + { > >> + auto exts = md.extents(); > >> + auto outer_shape = std::array{exts.extent(0), exts.extent(1), > >> exts.extent(2)}; > >> + > >> + constexpr auto full_index = inv_collapsed_index_map<Slices...>(); > >> + auto make_slice = [](size_t i, auto slice) > >> + { > >> + if constexpr (std::same_as<decltype(slice), collapse>) > >> + return i; > >> + else > >> + return slice; > >> + }; > >> + > >> + auto loop_body = [&]<size_t... J>(std::index_sequence<J...>, auto > ijk, > >> + auto... slices) > >> + { > >> + auto submd = submdspan(md, slices...[I]...); > >> + auto selection = std::tuple{make_selection(exts.extent(I), > >> slices...[I])...}; > >> + auto inner_shape = std::array<size_t, full_index.size()>{ > >> + std::get<full_index[J]>(selection).size()... > >> + }; > >> + > >> + for (auto ij : multi_index_generator(inner_shape)) > >> + { > >> + ((ijk[full_index[J]] = > get<full_index[J]>(selection)[ij[J]]),...); > >> + VERIFY(submd[ij] == md[ijk]); > >> + } > >> + }; > >> + > >> + for (auto ijk : multi_index_generator(outer_shape)) > >> + loop_body(std::make_index_sequence<full_index.size()>(), ijk, > >> + make_slice(ijk[I], slices...[I])...); > >> + return true; > >> + } > >> + > >> +template<typename Layout, typename...MD, typename... Slices> > >> + constexpr bool > >> + check_selection(std::mdspan<MD...> md, Slices... slices) > >> + { > >> + auto indices = std::make_index_sequence<sizeof...(slices)>(); > >> + return check_selection<Layout>(indices, md, slices...); > >> + } > >> + > >> +template<typename Layout, typename IndexType, size_t... Extents, > >> + typename... Slices> > >> + constexpr bool > >> + check_selection(std::extents<IndexType, Extents...>exts, Slices... > >> slices) > >> + { > >> + auto run = [&](auto m) > >> + { > >> + auto storage = std::vector<double>(m.required_span_size()); > >> + auto md = std::mdspan(storage.data(), m); > >> + fill(md); > >> + return check_selection<Layout>(md, slices...); > >> + }; > >> + > >> + if constexpr (std::same_as<Layout, std::layout_stride>) > >> + { > >> + auto m = typename Layout::mapping(exts, std::array{15, 2, 50}); > >> + return run(m); > >> + } > >> + else > >> + { > >> + auto m = typename Layout::mapping(exts); > >> + return run(m); > >> + } > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_scalar_selection(auto exts) > >> + { > >> + check_selection<Layout>(exts, collapse{}, collapse{}, collapse{}); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_full_lines(auto exts) > >> + { > >> + check_selection<Layout>(exts, all, collapse{}, collapse{}); > >> + check_selection<Layout>(exts, collapse{}, all, collapse{}); > >> + check_selection<Layout>(exts, collapse{}, collapse{}, all); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_full_blocks(auto exts) > >> + { > >> + check_selection<Layout>(exts, all, all, collapse{}); > >> + check_selection<Layout>(exts, all, collapse{}, all); > >> + check_selection<Layout>(exts, collapse{}, all, all); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_cubes(auto exts) > >> + { > >> + auto s0 = std::pair{0, 2}; > >> + auto s1 = std::pair{1, 4}; > >> + auto s2 = std::pair{3, 7}; > >> + > >> + check_selection<Layout>(exts, all, all, all); > >> + check_selection<Layout>(exts, all, all, s2); > >> + check_selection<Layout>(exts, s0, all, all); > >> + check_selection<Layout>(exts, s0, all, s2); > >> + check_selection<Layout>(exts, s0, s1, s2); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_line_selection(auto exts) > >> + { > >> + auto check = [&](auto s) > >> + { > >> + check_selection<Layout>(exts, collapse{}, s, collapse{}); > >> + }; > >> + > >> + check(std::strided_slice(0, 2, 2)); > >> + check(std::strided_slice(0, 3, 2)); > >> + check(std::strided_slice(1, 3, 2)); > >> + check(std::strided_slice(1, std::cw<3>, std::cw<2>)); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_box_selection(auto exts) > >> + { > >> + auto s0 = std::strided_slice(0, 3, 2); > >> + auto s1 = std::strided_slice(1, 4, 2); > >> + auto s2 = std::strided_slice(0, 7, 3); > >> + > >> + check_selection<Layout>(exts, s0, s1, s2); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_all_cheap() > >> + { > >> + constexpr auto dyn_exts = std::extents(3, 5, 7); > >> + constexpr auto sta_exts = std::extents<int, 3, 5, 7>{}; > >> + > >> + test_scalar_selection<Layout>(dyn_exts); > >> + test_scalar_selection<Layout>(sta_exts); > >> + static_assert(test_scalar_selection<Layout>(dyn_exts)); > >> + static_assert(test_scalar_selection<Layout>(sta_exts)); > >> + > >> + test_full_lines<Layout>(dyn_exts); > >> + test_full_lines<Layout>(sta_exts); > >> + static_assert(test_full_lines<Layout>(dyn_exts)); > >> + static_assert(test_full_lines<Layout>(sta_exts)); > >> + > >> + test_strided_box_selection<Layout>(dyn_exts); > >> + test_strided_box_selection<Layout>(sta_exts); > >> + static_assert(test_strided_box_selection<Layout>(dyn_exts)); > >> + static_assert(test_strided_box_selection<Layout>(sta_exts)); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_all_expensive() > >> + { > >> + auto run = [](auto exts) > >> + { > >> + test_full_blocks<Layout>(exts); > >> + test_cubes<Layout>(exts); > >> + }; > >> + > >> + run(std::extents(3, 5, 7)); > >> + run(std::extents<int, 3, 5, 7>{}); > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_all() > >> + { > >> + test_all_cheap<Layout>(); > >> + test_all_expensive<Layout>(); > >> + return true; > >> + } > >> + > >> +int > >> +main() > >> +{ > >> + test_all<std::layout_left>(); > >> + return 0; > >> +} > >> diff --git > >> > a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc > >> > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc > >> new file mode 100644 > >> index 00000000000..a37d3cd588f > >> --- /dev/null > >> +++ > >> > b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_mapping.cc > >> @@ -0,0 +1,136 @@ > >> +// { dg-do run { target c++26 } } > >> +#include <mdspan> > >> + > >> +#include <iostream> // TODO remove > >> +#include "../layout_traits.h" > >> +#include <testsuite_hooks.h> > >> + > >> +constexpr size_t dyn = std::dynamic_extent; > >> + > >> +template<typename Mapping, typename... Slices> > >> + constexpr auto > >> + call_submdspan_mapping(const Mapping& m, std::tuple<Slices...> > slices) > >> + { > >> + auto impl = [&]<size_t... I>(std::index_sequence<I...>) > >> + { return submdspan_mapping(m, get<I>(slices)...); }; > >> + return impl(std::make_index_sequence<sizeof...(Slices)>()); > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_layout_unpadded_return_types() > >> + { > >> + constexpr auto padding_side = > >> DeducePaddingSide::from_typename<Layout>(); > >> + using Traits = LayoutTraits<padding_side>; > >> + > >> + { > >> + auto m0 = typename Layout::mapping(std::extents()); > >> + auto result = submdspan_mapping(m0); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, Layout>); > >> + } > >> + > >> + auto exts = Traits::make_extents(std::dims<5, int>(3, 5, 7, 11, > 13)); > >> + auto m = typename Layout::mapping(exts); > >> + auto all = std::full_extent; > >> + auto s251 = std::strided_slice{2, 5, std::cw<1>}; > >> + > >> + { > >> + auto slices = std::tuple{0, 0, 0, 0, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, Layout>); > >> + } > >> + > >> + { > >> + auto slices = std::tuple{all, all, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, Layout>); > >> + } > >> + > >> + { > >> + auto s0 = std::strided_slice{1, 1, std::cw<1>}; > >> + auto slices = std::tuple{s0, all, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(is_same_padded<padding_side, layout_type>); > >> + } > >> + > >> + { > >> + auto s0 = std::strided_slice{1, 2, std::cw<1>}; > >> + auto slices = std::tuple{s0, all, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(is_same_padded<padding_side, layout_type>); > >> + } > >> + > >> + { > >> + auto s0 = std::strided_slice{1, 2, std::cw<1>}; > >> + auto slices = std::tuple{s0, 0, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = typename > decltype(result.mapping)::layout_type; > >> + static_assert(is_same_padded<padding_side, layout_type>); > >> + } > >> + > >> + { > >> + auto s0 = std::strided_slice{1, 2, 1}; > >> + auto slices = std::tuple{s0, all, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, std::layout_stride>); > >> + } > >> + > >> + { > >> + auto slices = std::tuple{1, all, all, s251, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, std::layout_stride>); > >> + } > >> + > >> + { > >> + auto s3 = std::strided_slice{2, std::cw<7>, std::cw<2>}; > >> + auto slices = std::tuple{all, all, all, s3, 0}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + using layout_type = decltype(result.mapping)::layout_type; > >> + static_assert(std::same_as<layout_type, std::layout_stride>); > >> + } > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_layout_unpadded_padding_value() > >> + { > >> + using Traits = > >> LayoutTraits<DeducePaddingSide::from_typename<Layout>()>; > >> + auto s0 = std::strided_slice{size_t(1), size_t(2), > >> std::cw<size_t(1)>}; > >> + auto s3 = std::strided_slice{size_t(2), size_t(5), > >> std::cw<size_t(1)>}; > >> + auto all = std::full_extent; > >> + > >> + auto check = [&](auto exts, size_t expected) > >> + { > >> + auto m = typename Layout::mapping(Traits::make_extents(exts)); > >> + auto slices = std::tuple{s0, size_t(0), all, s3, size_t(0)}; > >> + auto result = call_submdspan_mapping(m, > Traits::make_tuple(slices)); > >> + auto padding_value = decltype(result.mapping)::padding_value; > >> + VERIFY(padding_value == expected); > >> + }; > >> + > >> + check(std::extents(std::cw<3>, std::cw<5>, std::cw<7>, 11, 13), > 3*5); > >> + check(std::extents(std::cw<3>, std::cw<5>, 7, 11, 13), 3*5); > >> + check(std::extents(std::cw<3>, 5, 7, 11, 13), dyn); > >> + check(std::extents(3, 5, 7, 11, 13), dyn); > >> + return true; > >> + } > >> + > >> +int > >> +main() > >> +{ > >> + test_layout_unpadded_return_types<std::layout_left>(); > >> + static_assert(test_layout_unpadded_return_types<std::layout_left>()); > >> + > >> + test_layout_unpadded_padding_value<std::layout_left>(); > >> + > static_assert(test_layout_unpadded_padding_value<std::layout_left>()); > >> + return 0; > >> +} > >> + > >> diff --git > >> a/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc > >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc > >> new file mode 100644 > >> index 00000000000..4f9aad81cb7 > >> --- /dev/null > >> +++ > >> b/libstdc++-v3/testsuite/23_containers/mdspan/submdspan/submdspan_neg.cc > >> @@ -0,0 +1,102 @@ > >> +// { dg-do compile { target c++26 } } > >> +#include <mdspan> > >> + > >> +#include <vector> > >> + > >> +template<typename Layout, typename... Slices> > >> + constexpr bool > >> + check_slice_range(Slices... slices) > >> + { > >> + auto m = typename Layout::mapping<std::extents<int, 3, 5, 7>>{}; > >> + auto storage = std::vector<double>(m.required_span_size()); > >> + auto md = std::mdspan(storage.data(), m); > >> + > >> + auto submd = submdspan(md, slices...); // { dg-error > >> "expansion of" } > >> + (void) submd; > >> + return true; > >> + } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_int_under() > >> + { > >> + check_slice_range<Layout>(1, -1, 2); // { dg-error > >> "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_int_under<std::layout_left>()); // { dg-error > >> "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_int_over() > >> + { > >> + check_slice_range<Layout>(1, 5, 2); // { dg-error > >> "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_int_over<std::layout_left>()); // { dg-error > >> "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_tuple_under() > >> + { > >> + check_slice_range<Layout>(1, std::tuple{-1, 2}, 2); // { dg-error > >> "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_tuple_under<std::layout_left>()); // { dg-error > >> "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_tuple_reversed() > >> + { > >> + check_slice_range<Layout>(1, std::tuple{3, 2}, 2); // { dg-error > >> "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_tuple_reversed<std::layout_left>()); // { dg-error > >> "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_tuple_over() > >> + { > >> + check_slice_range<Layout>(1, std::tuple{0, 6}, 2); // { dg-error > >> "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_tuple_over<std::layout_left>()); // { dg-error > >> "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_slice_zero() > >> + { > >> + check_slice_range<Layout>(1, std::strided_slice{1, 1, 0}, 2); // { > >> dg-error "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_strided_slice_zero<std::layout_left>()); // { > >> dg-error "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_slice_offset_under() > >> + { > >> + check_slice_range<Layout>(1, std::strided_slice{-1, 1, 1}, 2); > // { > >> dg-error "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_strided_slice_offset_under<std::layout_left>()); > // > >> { dg-error "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_slice_offset_over() > >> + { > >> + check_slice_range<Layout>(1, std::strided_slice{6, 0, 1}, 2); > // { > >> dg-error "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_strided_slice_offset_over<std::layout_left>()); > // { > >> dg-error "expansion of" } > >> + > >> +template<typename Layout> > >> + constexpr bool > >> + test_strided_slice_extent_over() > >> + { > >> + check_slice_range<Layout>(1, std::strided_slice{1, 5, 1}, 2); > // { > >> dg-error "expansion of" } > >> + return true; > >> + } > >> +static_assert(test_strided_slice_extent_over<std::layout_left>()); > // { > >> dg-error "expansion of" } > >> + > >> +// { dg-prune-output "static assertion failed" } > >> +// { dg-prune-output "__glibcxx_assert_fail" } > >> +// { dg-prune-output "non-constant condition" } > >> -- > >> 2.51.2 > >> > >> > > > >
