On 9/9/25 12:00 PM, Tomasz Kaminski wrote:
On Mon, Sep 8, 2025 at 10:14 PM Luc Grosheintz <luc.groshei...@gmail.com> wrote:I decided to deviate once (described in detail in the commit message). Without this deviation writing tests is very clumsy and sometimes not possible, e.g. when mixing uint8_t and dynamic padding values to check mandates w.r.t. representability. Previously, we discussed other ways of fixing the issue. The reason I chose this version is because I believe it's the smallest deviation from the standard; and we can deviate differently in followup commits. --------------------------------------------------------- The tests contain a function: constexpr bool is_same_mapping(const auto& lhs, const auto& rhs) // ... for (size_t i = 0; i < lhs.extents().rank(); ++i) if (lhs.stride(i) != rhs.stride(i)) return false; return true; } The above is accidentally quadratic, because computing all strides can be done in linear time, but not if one computes them one by one, then it's quadratic. It's annoyingly hard to do in linear time, because it requires both precise knowledge of the definition of `stride` for each layout; and ideally it also requires access to implementation details related to how we separate static and dynamic extents to be able to compute static products at compile time. Do we need a member strides() for all strided layout mappings to avoid the issue? --------------------------------------------------------- The left-padded conversion rules are unexpected in two ways: 1. It allows conversion between mappings that have extent_types that aren't convertable (because it's not guaranteed at compile-time that the conversion is safe). AFAICT, this is covered by LWG4272 [1]. [1]: https://cplusplus.github.io/LWG/issue4272 2. The rules for converting between left-padded layouts with different padding values is different. For extents the rule is to allow sta -> sta, sta -> dyn and dyn -> dyn; but prevent dyn -> sta (because it can't be guaranteed at compile-time that the dynamic padding values matches the dynamic padding value match). For padding values we also disallow conversion for dyn -> dyn. --------------------------------------------------------- If the padding value is statically 0 or 1, then it's effectively never padded, because: least_multiple_as_least(padding_value, extent(0)) == extent(0) either because of a special case (for padding_value == 0), or because `n % 1 == 0` and `n / 1 == n` (for padding_value == 1). Put differently, it's always exhaustive. However, is_always_exhaustive isn't true if exts.extent(0) is a dynamic extent. Even though that has no impact for padding_value <= 1. --------------------------------------------------------- It's not possible to round-trip: layout_right -> layout_left_padded -> layout_right even for rank <= 1 where usually the difference between different mappings is ignored. --------------------------------------------------------- There's similar problems with operator== as noted before; and for which I promised to (and never did) report a non-editorial issue. --- 8< --- This commit adds a new layout layout_left_padded as standardized in N5014 but with one deviation. This includes checking of mandates and prerequisites, with exception of "slow" prerequisites, i.e. those that requires a for loop. It adds the feature testing macro submdspan as a purely internal macro and registers layout_left_padded in the std module. The standard mandates that the padding_value is representable as index_type. However, padding_value can be dynamic_extent which is defined as `size_t(-1)`. Hence, it's impossible to use a dynamic padding_value if index_type is int. The deviation is to only require static padding values to be representable as index_type; and thereby allow dynamic padding values for every index_type. libstdc++-v3/ChangeLog: * include/bits/version.def (submdspan): Add as internal only. * include/bits/version.h: Regenerate. * include/std/mdspan (__fwd_prod): New overload. (layout_left_padded): Add declaration and implementation. (layout_right_padded): Add declaration only. (__is_left_padded_mapping): New concept. (__is_right_padded_mapping): Ditto. (__standardized_mapping): Recognize left and right padded mappings. (layout_left::mapping::mapping): New overload for left padded mappings. * src/c++23/std.cc.in (layout_left_padded): Add. * testsuite/23_containers/mdspan/layouts/ctors.cc: Add tests for layout_left_padded. * testsuite/23_containers/mdspan/layouts/empty.cc: Ditto. * testsuite/23_containers/mdspan/layouts/mapping.cc: Ditto. * testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc: New test. * testsuite/23_containers/mdspan/layouts/padded.cc: New test. * testsuite/23_containers/mdspan/layouts/padded_neg.cc: New test. Signed-off-by: Luc Grosheintz <luc.groshei...@gmail.com>Thanks for the patch. I have looked through the implementation, and left a few suggestions there. After merging this, I would like to look if we can merge the functions for indexing and producs somehow with the non-padded facilities. My general idea would be passing an stride there.--- libstdc++-v3/include/bits/version.def | 9 + libstdc++-v3/include/bits/version.h | 9 + libstdc++-v3/include/std/mdspan | 416 +++++++++++++- libstdc++-v3/src/c++23/std.cc.in | 8 +- .../23_containers/mdspan/layouts/ctors.cc | 61 +- .../mdspan/layouts/debug/padded_neg.cc | 3 + .../23_containers/mdspan/layouts/empty.cc | 10 +- .../23_containers/mdspan/layouts/mapping.cc | 198 +++++-- .../23_containers/mdspan/layouts/padded.cc | 537 ++++++++++++++++++ .../mdspan/layouts/padded_neg.cc | 225 ++++++++ 10 files changed, 1414 insertions(+), 62 deletions(-) create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc diff --git a/libstdc++-v3/include/bits/version.def b/libstdc++-v3/include/bits/version.def index 65b9a278776..1895078210a 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1050,6 +1050,15 @@ ftms = { cxxmin = 26; extra_cond = "__glibcxx_assume_aligned " "&& __glibcxx_is_sufficiently_aligned"; + }; +}; + +ftms = { + name = submdspan;The fact that this uses a submdspan macro is very unfortunate. I think instead of using submdspan, I would add an internal no_stdname padded_layouts macro, that will keep using, even after defining submdspan.+ no_stdname = true; // FIXME: remove+ values = { + v = 202306;Also use value 1 here, and add TODO to update.+ cxxmin = 26; }; }; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index b05249857d2..c6de494da08 100644 --- a/libstdc++-v3/include/bits/version.h +++ b/libstdc++-v3/include/bits/version.h @@ -1178,6 +1178,15 @@ #endif /* !defined(__cpp_lib_aligned_accessor) && defined(__glibcxx_want_aligned_accessor) */ #undef __glibcxx_want_aligned_accessor +#if !defined(__cpp_lib_submdspan) +# if (__cplusplus > 202302L) +# define __glibcxx_submdspan 202306L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_submdspan) +# endif +# endif +#endif /* !defined(__cpp_lib_submdspan) && defined(__glibcxx_want_submdspan) */ +#undef __glibcxx_want_submdspan + #if !defined(__cpp_lib_ssize) # if (__cplusplus >= 202002L) # define __glibcxx_ssize 201902L diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index 678b2619ebe..1270640ab6b 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -44,6 +44,7 @@ #define __glibcxx_want_mdspan #define __glibcxx_want_aligned_accessor +#define __glibcxx_want_submdspan #include <bits/version.h> #ifdef __glibcxx_mdspan @@ -476,6 +477,21 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION } // Preconditions: _r < _Extents::rank() + template<typename _Extents> + constexpr typename _Extents::index_type + __fwd_prod(const _Extents& __exts, size_t __begin, size_t __end) noexcept + {For the future (not in this patch), I would like us to experiment in defining __fwd_prod as follows: template<typename _Extents, size_t __r) __fwd_prod(const _Extents& __exts, size_t __stride1, size_t __r) noexcept { if constexpr (__rank == 1) return 1; else if (__r == 0) return 1; else if constexpr (__rank == 2) return __stride1; else if (__r == 1) return __stride1; else if constexpr (__all_dynamic(std::span(__sta_exts).substr(1, __rank-1))) return __stride1 * __extents_prod(__exts, 1, 1, __r); else { size_t __sta_prod = __fwd_partial_prods<__sta_exts>[__r-2]; return __stride1 * __extents_prod(__exts, __sta_prod, 1, __r); } } Then reduce __fwd_partial_prods to contain multiplication without the first two dimensions. Then we call it passing extent(1) or __stride1. We could also mark as __always_inline__.+ size_t __sta_prod = [__begin, __end] { + span<const size_t> __sta_exts = __static_extents<_Extents>(); + size_t __ret = 1; + for(auto __ext : __sta_exts.subspan(__begin, __end - __begin)) + if (__ext != dynamic_extent) + __ret *= __ext; + return __ret; + }(); + return __extents_prod(__exts, __sta_prod, __begin, __end); + } + template<typename _Extents> constexpr typename _Extents::index_type __fwd_prod(const _Extents& __exts, size_t __r) noexcept @@ -567,6 +583,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class mapping; }; +#ifdef __glibcxx_submdspan + template<size_t _PaddingValue> + struct layout_left_padded + { + template<typename _Extents> + class mapping; + }; + + template<size_t _PaddingValue> + struct layout_right_padded + { + template<typename _Extents> + class mapping; + }; +#endif + namespace __mdspan { template<typename _Tp> @@ -660,10 +692,42 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION is_same_v<typename _Layout::template mapping<typename _Mapping::extents_type>, _Mapping>; + template<template<size_t> typename _Layout, typename _Mapping> + concept __is_padded_mapping_of = requires I think this could be equialy implemented as: = __mapping_of<_Layout<_Mapping::padding_value>, _Mapping>; + { + typename _Mapping::extents_type; + { _Mapping::padding_value } -> same_as<const size_t&>; + } + && same_as<_Mapping, typename _Layout<_Mapping::padding_value>::mapping< + typename _Mapping::extents_type>>; + +#ifdef __glibcxx_submdspan + template<typename _Mapping> + constexpr bool __is_left_padded_mapping = __is_padded_mapping_of< + layout_left_padded, _Mapping>; + + template<typename _Mapping> + constexpr bool __is_right_padded_mapping = __is_padded_mapping_of< + layout_right_padded, _Mapping>; +#elseI would remove this definitions.+ template<typename _Mapping> + constexpr bool __is_left_padded_mapping = false; + + template<typename _Mapping> + constexpr bool __is_right_padded_mapping = false; +#endif + + template<typename _PaddedMapping> + constexpr size_tThis can be consteval.+ __get_static_stride()+ { return _PaddedMapping::_S_static_stride; }+ template<typename _Mapping> concept __standardized_mapping = __mapping_of<layout_left, _Mapping> || __mapping_of<layout_right, _Mapping> - || __mapping_of<layout_stride, _Mapping>; + || __mapping_of<layout_stride, _Mapping> + || __is_left_padded_mapping<_Mapping> + || __is_right_padded_mapping<_Mapping>;And I would add if def here: template<typename _Mapping> concept __standardized_mapping = __mapping_of<layout_left, _Mapping> || __mapping_of<layout_right, _Mapping> - || __mapping_of<layout_stride, _Mapping> #ifdef __glibcxx_submdspan __is_padded_mapping_of<layout_left_padded, _Mapping> __is_padded_mapping_of<layout_right_padded, _Mapping> #endif __glibcxx_submdspan ; // semicolon// A tag type to create internal ctors. class __internal_ctor @@ -717,6 +781,28 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : mapping(__other.extents(), __mdspan::__internal_ctor{}) { __glibcxx_assert(*this == __other); } This should be under __glibcxx_submdspan macro.+ template<class _LeftPaddedMapping> + requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping> + && is_constructible_v<extents_type, + typename _LeftPaddedMapping::extents_type> + constexpr + explicit(!is_convertible_v<typename _LeftPaddedMapping::extents_type, + extents_type>) + mapping(const _LeftPaddedMapping& __other) noexcept + : mapping(__other.extents(), __mdspan::__internal_ctor{}) + { + constexpr size_t __ostride_sta = __mdspan::__get_static_stride< + _LeftPaddedMapping>(); + + if constexpr (extents_type::rank() > 1 + && extents_type::static_extent(0) != dynamic_extent + && __ostride_sta != dynamic_extent) + static_assert(extents_type::static_extent(0) == __ostride_sta); + + __glibcxx_assert(extents_type::rank() <= 1 + || __other.stride(1) == __other.extents().extent(0));+ }+ constexpr mapping& operator=(const mapping&) noexcept = default; @@ -1164,6 +1250,334 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION [[no_unique_address]] _S_strides_t _M_strides; }; +#ifdef __glibcxx_submdspan + namespace __mdspan + { + template<typename _Io, typename _I1, typename _I2> + constexpr _IoI would use size_t here, see below.+ __least_multiple_at_least(_I1 __x, _I2 __y) + { + if (__x == 0) + return static_cast<_Io>(__y); + return static_cast<_Io>((__y / __x + (__y % __x != 0)) * __x);I think this could be implemented as: ((__y + __x - 1) / __x) * __x;
This was the first version; but while reading io_uring examples, this variant kept coming up. IIUC, the problem is overflow. For example assume `size_t` is uint8_t, i.e. 255 is the largest representable value. Then, __least_multiple_at_least(5, 254) == (254 + 5 - 1) / 5 * 5 == (258) / 5 * 5 == 2 / 5 * 5 == 0 whereas we'd like it to be 255. I think the trick is sound if __x were a power of two; but not for other values of __x. Good news, if the compiler knows __x is a power of two, then division and modulo are cheap (i.e. a shift and mask, respectively).
+ } + + template<typename _Extents, typename _Stride, typename... _Indices> + constexpr typename _Extents::index_type + __linear_index_leftpad(const _Extents& __exts, _Stride __stride, + _Indices... __indices) + { + // i0 + stride*(i1 + extents.extent(1)*...) + using _IndexType = typename _Extents::index_type; + _IndexType __res = 0;+ if constexpr (sizeof...(__indices) > 0)+ { + _IndexType __mult = 1; + + auto __update_rest = [&, __pos = 1u](_IndexType __idx) mutable + { + __res += __idx * __mult; + __mult *= __exts.extent(__pos); + ++__pos; + }; + + auto __update = [&](_IndexType __idx, auto... __rest) + { + __res += __idx; + __mult = __stride.extent(0); + (__update_rest(__rest), ...); + }; + __update(__indices...); + } + return __res; + } + } + + template<size_t _PaddingValue> + template<typename _Extents> + class layout_left_padded<_PaddingValue>::mapping + { + public: + static constexpr size_t padding_value = _PaddingValue; + + using extents_type = _Extents; + using index_type = typename extents_type::index_type; + using size_type = typename extents_type::size_type; + using rank_type = typename extents_type::rank_type; + using layout_type = layout_left_padded<padding_value>; + + static_assert(__mdspan::__representable_size<extents_type, index_type>, + "The size of extents_type must be representable as index_type"); + + // DEVIATION the standard requires that _PaddingValue is representable + // as index_type. This seems unlikely, because if index_type < size_t + // then the mandate is violated for `padding_value == dynamic_extent`. + // Which means that padding_value == dynamic_extent only reliably work + // if index_type == size_t (e.g. it fails for index_type == int on a + // 64bit system). + static_assert((padding_value == dynamic_extent) + || (padding_value <= numeric_limits<index_type>::max()), + "padding_value must be representable as index_type");We are missing this mandate from here: https://eel.is/c++draft/mdspan.layout#leftpad.overview-5.3 https://eel.is/c++draft/mdspan.layout#leftpad.overview-5.4. Because the static_stride is always greater or equal extent, I would suggest adding start index to static quotient, and having: __static_quotient<_Extents, _IndexType>(1) / static_stride(). Of cource with check for contains_zero etc.
Thank you! Somehow I thought these were super hard, but they're not :) There's many more of those, in form of pre-requisites, I did those too.
+ + private: + static constexpr size_t _S_rank = _Extents::rank(); + static constexpr size_t _S_static_stride = [] consteval + { + if constexpr (_S_rank <= 1) + return size_t(0); + else if constexpr (padding_value == dynamic_extent || + _Extents::static_extent(0) == dynamic_extent) + return dynamic_extent; + else + return __mdspan::__least_multiple_at_least<size_t>(padding_value, + size_t(_Extents::static_extent(0))); + }(); + + constexpr friend size_t + __mdspan::__get_static_stride<mapping>(); + + public: + constexpr + mapping() noexcept + : mapping(extents_type{}) + { } + + constexpr + mapping(const mapping&) noexcept = default; + + constexpr + mapping(const extents_type& __exts) + : _M_extents(__exts) + { + __glibcxx_assert(__mdspan::__is_representable_extents(_M_extents)); + if (_S_rank > 1) + { + index_type __stride; + if constexpr (padding_value == dynamic_extent) + __stride = __exts.extent(0); + elseWe should have another if constexpr branch, where _S_static_stride is not equal dynamic_extent, then we do not need to do runtime computation: if constexpr(_S_static_stride != dynamic_extent) return else { .... }+ __stride = __mdspan::__least_multiple_at_least<index_type>( + padding_value, __exts.extent(0));Check if this value can fit.+ + _M_stride = std::extents<index_type, _S_static_stride>{__stride};Use _S_stride_type here.+ } + } + + template<__mdspan::__valid_index_type<index_type> _OIndexType> + constexpr mapping(const extents_type& __exts, _OIndexType __opad) + : _M_extents(__exts) + { + if constexpr (std::is_integral_v<_OIndexType>) + { + __glibcxx_assert(cmp_less_equal(__opad, + numeric_limits<index_type>::max())); + if constexpr (std::is_signed_v<_OIndexType>) + __glibcxx_assert(__opad >= 0); + } + + auto __pad = static_cast<index_type>(std::move(__opad)); + if constexpr (std::is_signed_v<index_type>) + __glibcxx_assert(__pad >= 0); + if constexpr (padding_value != dynamic_extent) + __glibcxx_assert(cmp_equal(padding_value, __pad)); + if constexpr (_S_rank > 1)This should be combined with _S_static_stride != dynamic_extent, we do not need to assign _Stride_type in that case, and more importantly compute __least_multiple_at_least..>+ _M_stride = _S_stride_type{ + __mdspan::__least_multiple_at_least<index_type>(__pad, + __exts.extent(0))}; + } + + template<typename _OExtents> + requires is_constructible_v<extents_type, _OExtents> + constexpr explicit(!is_convertible_v<_OExtents, extents_type>) + mapping(const layout_left::mapping<_OExtents>& __other) + : mapping(extents_type(__other.extents())) + { + if constexpr (_OExtents::rank() > 1) + static_assert(_S_static_stride == dynamic_extent + || _OExtents::static_extent(0) == dynamic_extent + || _S_static_stride == _OExtents::static_extent(0), + "(*this).stride(1) must be compatible with other.stride(1)"); + + // REVIEW: can this one go, because we checkYeas, remove it.+ // __is_representable_extents in mapping(const mapping&) + __glibcxx_assert(cmp_less_equal(__other.required_span_size(), + numeric_limits<index_type>::max())); + __glibcxx_assert((!(_S_rank > 1 && padding_value != dynamic_extent)) + || (_M_stride.extent(0) == __other.extents().extent(0))); + } + + template<typename _OExtents> + requires is_constructible_v<_OExtents, extents_type> + constexpr explicit(_OExtents::rank() > 0) + mapping(const typename layout_stride::mapping<_OExtents>& __other) + : _M_extents(__other.extents()) + { + if constexpr (_S_rank > 1 && padding_value != dynamic_extent) + __glibcxx_assert(cmp_equal(__other.stride(1), + __mdspan::__least_multiple_at_least<index_type>(padding_value, + __other.extents().extent(0))));Replace this check is similar to the manner I have described below, using strid(1), or _M_stride.
Not sure, see below.
+ + if constexpr (_S_rank > 0) + __glibcxx_assert(__other.stride(0) == 1); + + __glibcxx_assert(cmp_less_equal(__other.required_span_size(), + numeric_limits<index_type>::max())); + + if constexpr (_S_rank > 1 && _S_static_stride == dynamic_extent) + _M_stride = _S_stride_type{__other.stride(1)}; + } + + template<typename _LeftPaddedMapping> + requires __mdspan::__is_left_padded_mapping<_LeftPaddedMapping> + && is_constructible_v<extents_type, + typename _LeftPaddedMapping::extents_type> + constexpr explicit(_S_rank > 1 && (padding_value != dynamic_extent + || _LeftPaddedMapping::padding_value == dynamic_extent)) + mapping(const _LeftPaddedMapping& __other) + : _M_extents(__other.extents()) + { + if constexpr (_S_rank > 1) + { + static_assert(padding_value == dynamic_extent + || _LeftPaddedMapping::padding_value == dynamic_extent + || padding_value == _LeftPaddedMapping::padding_value, + "If neither padding_value is dynamic_extent, then they must " + "be equal"); + + __glibcxx_assert((padding_value == dynamic_extent + || cmp_equal(__other.stride(1), + __mdspan::__least_multiple_at_least<index_type>( + padding_value, __other.extents().extent(0)))) + && "other.stride(1) must be the next larger multiple of " + "padding_value that is greater or equal to other.extent(0)");I think this checks can be make conditionally as follows: if constexpr (_S_static_stride != dynamic_extent) assert(cmp_equal(__other.stride(1), _S_static_stride) else if constexpr (padding_value != dynamic_extent) asssert(cmp_equal(other.stride(1), this.stride(1)); And do them after initializing _M_stride.
I might be missing something, but it sounds like: _M_stride = other.stride(1); assert(other.stride(1) == _M_stride); (which is trivially true, and doesn't assert internal consistency).
+ }+ __glibcxx_assert(cmp_less_equal(__other.required_span_size(), + numeric_limits<index_type>::max())); + + if constexpr (_S_rank > 1 && (_S_static_stride == dynamic_extent)) + _M_stride = _S_stride_type{__other.stride(1)}; + } + + template<typename _RightPaddedMapping> + requires (__mdspan::__is_right_padded_mapping<_RightPaddedMapping> + || __mdspan::__mapping_of<layout_right, _RightPaddedMapping>) + && (_S_rank <= 1) + && is_constructible_v<extents_type, + typename _RightPaddedMapping::extents_type> + constexpr explicit(!is_convertible_v< + typename _RightPaddedMapping::extents_type, extents_type>) + mapping(const _RightPaddedMapping& __other) noexcept + : _M_extents(__other.extents()) + { + __glibcxx_assert(cmp_less_equal(__other.required_span_size(), + numeric_limits<index_type>::max())); + } + + constexpr mapping& + operator=(const mapping&) noexcept = default; + + constexpr const extents_type& + extents() const noexcept { return _M_extents; } + + constexpr array<index_type, _S_rank> + strides() const noexcept + { + array<index_type, _S_rank> __ret; + if constexpr (_S_rank > 0) + __ret[0] = 1; + if constexpr (_S_rank > 1) + __ret[1] = _M_stride.extent(0); + if constexpr (_S_rank > 2) + for(size_t __i = 2; __i < _S_rank; ++__i) + __ret[__i] = __ret[__i - 1] * _M_extents.extent(__i - 1); + return __ret; + } + + constexpr index_type + required_span_size() const noexcept + { + if constexpr (_S_rank == 0) + return 1; + else if (__mdspan::__empty(_M_extents)) + return 0; + else + return _M_stride.extent(0) * __mdspan::__rev_prod(_M_extents, 0) + - (_M_stride.extent(0) - _M_extents.extent(0));It took me some time to realize, but indeed this looks correct.+ } + + // _GLIBCXX_RESOLVE_LIB_DEFECTS + // 4314. Missing move in mdspan layout mapping::operator() + template<__mdspan::__valid_index_type<index_type>... _Indices> + requires (sizeof...(_Indices) == _S_rank) + constexpr index_type + operator()(_Indices... __indices) const noexcept + { + return __mdspan::__linear_index_leftpad(_M_extents, _M_stride, + static_cast<index_type>(std::move(__indices))...); + } + + static constexpr bool + is_always_unique() noexcept { return true; } + + static constexpr bool + is_always_exhaustive() noexcept + { + if constexpr (_S_rank <= 1) + return true; + else + return extents_type::static_extent(0) != dynamic_extent + && _S_static_stride != dynamic_extent + && extents_type::static_extent(0) == _S_static_stride; + } + + static constexpr bool + is_always_strided() noexcept { return true; } + + static constexpr bool + is_unique() noexcept { return true; } + + constexpr bool + is_exhaustive() const noexcept + { + if constexpr (is_always_exhaustive()) + return true; + else + return _M_extents.extent(0) == _M_stride.extent(0); + } + + static constexpr bool + is_strided() noexcept { return true; } + + constexpr index_type + stride(rank_type __r) const noexcept + { + __glibcxx_assert(__r < _S_rank); + if (__r == 0) + return 1; + else + return static_cast<index_type>( + static_cast<size_t>(_M_stride.extent(0)) * + static_cast<size_t>(__mdspan::__fwd_prod(_M_extents, 1, __r))); + } + + template<typename _LeftPaddedMapping> + requires(__mdspan::__is_left_padded_mapping<_LeftPaddedMapping> + && _LeftPaddedMapping::extents_type::rank() == _S_rank) + friend constexpr bool + operator==(const mapping& __self, const _LeftPaddedMapping& __other) + noexcept + { + return __self.extents() == __other.extents() + && (_S_rank < 2 || __self.stride(1) == __other.stride(1)); + } + + private: + using _S_stride_type = std::extents<index_type, _S_static_stride>;Hmm, that interesting approach to storing stride. I was expecting integral_constant here, but this seems to also work ok.
It a tip in the standard itself :)
+ [[no_unique_address]] _S_stride_type _M_stride; + [[no_unique_address]] extents_type _M_extents{}; + }; +#endif + template<typename _ElementType> struct default_accessor { diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ std.cc.in index a217a87330b..c2854cb1828 100644 --- a/libstdc++-v3/src/c++23/std.cc.in +++ b/libstdc++-v3/src/c++23/std.cc.in @@ -1869,9 +1869,11 @@ export namespace std using std::aligned_accessor; #endif using std::mdspan; - // FIXME layout_left_padded, layout_right_padded, strided_slice, - // submdspan_mapping_result, full_extent_t, full_extent, submdspan_extents, - // mdsubspan +#if __glibcxx_submdspan + using std::layout_left_padded; +#endif + // FIXME layout_right_padded, strided_slice, submdspan_mapping_result, + // full_extent_t, full_extent, submdspan_extents, mdsubspan } #endif diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc index 23c0a55dae1..290c53647cf 100644 --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc @@ -6,6 +6,16 @@ constexpr size_t dyn = std::dynamic_extent; +template<typename Layout> + constexpr bool + is_layout_leftpad = false; + +#ifdef __glibcxx_submdspan +template<size_t Pad> + constexpr bool + is_layout_leftpad<std::layout_left_padded<Pad>> = true; +#endif + template<typename Mapping, typename IndexType, size_t... Extents> constexpr void verify(std::extents<IndexType, Extents...> oexts) @@ -27,7 +37,6 @@ template<typename Mapping, typename OMapping> VERIFY(std::cmp_equal(m.stride(i), other.stride(i))); } - template<typename To, typename From> constexpr void verify_convertible(From from) @@ -40,7 +49,10 @@ template<typename To, typename From> constexpr void verify_nothrow_convertible(From from) { - static_assert(std::is_nothrow_constructible_v<To, From>); + if constexpr (is_layout_leftpad<typename To::layout_type>)I would prefer if particular test cases were adjusted, instead of having generic workaround for leftpad. The difference looks a bit meaningless otherwise. Or, maybe create a separate patch, that's add noexcept (as extension) where they would be needed to pass the test.
It's certainly clumsy, but the pattern is that anything that's C++23 is likely noexcept, while anything that was added in C++26 is likely not noexcept. This approach is pretty explicit about it and avoid repeating that logic thoughout the file.
Even if the first commit, I would make constructor from extents only noexcept, as it does not even have precondtion.
The ctor mapping(const extents_type&) has three preconditions: https://eel.is/c++draft/mdspan.layout#leftpad.cons-1
+ static_assert(std::is_constructible_v<To, From>); + else + static_assert(std::is_nothrow_constructible_v<To, From>); verify_convertible<To>(from); } @@ -57,7 +69,10 @@ template<typename To, typename From> constexpr void verify_nothrow_constructible(From from) { - static_assert(std::is_nothrow_constructible_v<To, From>); + if constexpr (is_layout_leftpad<typename To::layout_type>) + static_assert(std::is_constructible_v<To, From>); + else + static_assert(std::is_nothrow_constructible_v<To, From>); verify_constructible<To>(from); } @@ -196,6 +211,16 @@ namespace from_extents // ctor: mapping(mapping<OExtents>) namespace from_same_layout { + template<typename Layout, typename Extents, typename OExtents> + constexpr void + verify_convertible(OExtents exts) + { + using Mapping = typename Layout::mapping<Extents>; + using OMapping = typename Layout::mapping<OExtents>; + + ::verify_convertible<Mapping>(OMapping(exts)); + } + template<typename Layout, typename Extents, typename OExtents> constexpr void verify_nothrow_convertible(OExtents exts) @@ -223,8 +248,12 @@ namespace from_same_layout verify_nothrow_convertible<Layout, std::extents<unsigned int>>( std::extents<int>{}); - verify_nothrow_constructible<Layout, std::extents<int>>( - std::extents<unsigned int>{}); + if constexpr (!is_layout_leftpad<Layout>) + verify_nothrow_constructible<Layout, std::extents<int>>( + std::extents<unsigned int>{}); + else + verify_convertible<Layout, std::extents<int>>( + std::extents<unsigned int>{}); assert_not_constructible< typename Layout::mapping<std::extents<int>>, @@ -234,8 +263,12 @@ namespace from_same_layout typename Layout::mapping<std::extents<int, 1>>, typename Layout::mapping<std::extents<int>>>(); - verify_nothrow_constructible<Layout, std::extents<int, 1>>( - std::extents<int, dyn>{1}); + if constexpr (!is_layout_leftpad<Layout>) + verify_nothrow_constructible<Layout, std::extents<int, 1>>( + std::extents<int, dyn>{1});+ else+ verify_convertible<Layout, std::extents<int, 1>>( + std::extents<int, dyn>{1}); verify_nothrow_convertible<Layout, std::extents<int, dyn>>( std::extents<int, 1>{}); @@ -247,8 +280,12 @@ namespace from_same_layout verify_nothrow_constructible<Layout, std::extents<int, 1, 2>>( std::extents<int, dyn, 2>{1}); - verify_nothrow_convertible<Layout, std::extents<int, dyn, 2>>( - std::extents<int, 1, 2>{}); + if constexpr (!is_layout_leftpad<Layout>) + verify_nothrow_convertible<Layout, std::extents<int, dyn, 2>>( + std::extents<int, 1, 2>{}); + else + verify_nothrow_constructible<Layout, std::extents<int, dyn, 2>>( + std::extents<int, 1, 2>{}); return true; } @@ -429,6 +466,12 @@ main() { test_all<std::layout_left>(); test_all<std::layout_right>(); +#ifdef __glibcxx_submdspan + test_all<std::layout_left_padded<0>>(); + test_all<std::layout_left_padded<1>>(); + test_all<std::layout_left_padded<2>>(); + test_all<std::layout_left_padded<dyn>>(); +#endif from_left_or_right::test_all<std::layout_left, std::layout_right>(); from_left_or_right::test_all<std::layout_right, std::layout_left>(); diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc new file mode 100644 index 00000000000..d4f9003fba6 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc @@ -0,0 +1,3 @@ +// { dg-do compile { target c++26 } } +// { dg-require-debug-mode "" } +#include<mdspan>It seems that the test are missing here.
Sorry, this file shouldn't exist. There's no new debug assertions.
diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc index 655b9b6d6c3..d1aff172b64 100644 --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc @@ -35,7 +35,8 @@ test_static_overflow() { constexpr Int n1 = std::numeric_limits<Int>::max(); constexpr size_t n2 = std::dynamic_extent - 1; - constexpr size_t n = std::cmp_less(n1, n2) ? size_t(n1) : n2; + // Allow some room for padding. + constexpr size_t n = (std::cmp_less(n1, n2) ? size_t(n1) : n2) - 4; verify_all(typename Layout::mapping<std::extents<Int, n, n, 0, n, n>>{}); verify_all(typename Layout::mapping<std::extents<Int, 0, n, n, n>>{}); @@ -73,7 +74,8 @@ test_dynamic_overflow() { constexpr Int n1 = std::numeric_limits<Int>::max(); constexpr size_t n2 = std::dynamic_extent - 1; - constexpr Int n = std::cmp_less(n1, n2) ? n1 : Int(n2); + // Allow some room for padding. + constexpr Int n = (std::cmp_less(n1, n2) ? n1 : Int(n2)) - 4; verify_all(make_mapping<Layout>( std::extents<Int, dyn, dyn, 0, dyn, dyn>{n, n, n, n})); @@ -127,5 +129,9 @@ main() static_assert(test_all<std::layout_left>()); static_assert(test_all<std::layout_right>()); static_assert(test_all<std::layout_stride>()); +#ifdef __glibcxx_submdspan + static_assert(test_all<std::layout_left_padded<1>>()); + static_assert(test_all<std::layout_left_padded<2>>()); +#endif return 0; } diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc index 58bce514435..3b9719eaf5d 100644 --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc @@ -7,6 +7,15 @@ constexpr size_t dyn = std::dynamic_extent; +template<typename Mapping> + concept has_static_is_exhaustive = requires + { + { Mapping::is_exhaustive() } -> std::same_as<bool>; + }; + +static_assert(has_static_is_exhaustive<std::layout_right::mapping<std::extents<int>>>); +static_assert(!has_static_is_exhaustive<std::layout_stride::mapping<std::extents<int>>>); + template<typename Layout, typename Extents> constexpr bool test_mapping_properties() @@ -32,7 +41,7 @@ template<typename Layout, typename Extents> static_assert(M::is_always_unique() && M::is_unique()); static_assert(M::is_always_strided() && M::is_strided()); - if constexpr (!std::is_same_v<Layout, std::layout_stride>) + if constexpr (has_static_is_exhaustive<M>) static_assert(M::is_always_exhaustive() && M::is_exhaustive()); return true; } @@ -306,7 +315,7 @@ template<typename Layout> constexpr void test_stride_1d() { - std::layout_left::mapping<std::extents<int, 3>> m; + typename Layout::mapping<std::extents<int, 3>> m; VERIFY(m.stride(0) == 1); } @@ -321,73 +330,150 @@ template<> } template<typename Layout> - constexpr void - test_stride_2d(); +struct TestStride2D; template<> - constexpr void - test_stride_2d<std::layout_left>() + struct TestStride2D<std::layout_left> { - std::layout_left::mapping<std::extents<int, 3, 5>> m; - VERIFY(m.stride(0) == 1); - VERIFY(m.stride(1) == 3); - } + static constexpr void + run() + { + std::layout_left::mapping<std::extents<int, 3, 5>> m; + VERIFY(m.stride(0) == 1); + VERIFY(m.stride(1) == 3); + } + }; template<> - constexpr void - test_stride_2d<std::layout_right>() + struct TestStride2D<std::layout_right> { - std::layout_right::mapping<std::extents<int, 3, 5>> m; - VERIFY(m.stride(0) == 5); - VERIFY(m.stride(1) == 1); - } + static constexpr void + run() + { + std::layout_right::mapping<std::extents<int, 3, 5>> m; + VERIFY(m.stride(0) == 5); + VERIFY(m.stride(1) == 1); + } + }; template<> + struct TestStride2D<std::layout_stride> + { + static constexpr void + run() + { + std::array<int, 2> strides{13, 2}; + std::layout_stride::mapping m(std::extents<int, 3, 5>{}, strides); + VERIFY(m.stride(0) == strides[0]); + VERIFY(m.stride(1) == strides[1]); + VERIFY(m.strides() == strides); + } + }; + +#if __glibcxx_submdspan +template<size_t PaddingValue> + struct TestStride2D<std::layout_left_padded<PaddingValue>> + { + static constexpr void + run() + { + using Layout = std::layout_left_padded<PaddingValue>; + typename Layout::mapping<std::extents<int, 3, 5>> m; + + VERIFY(m.stride(0) == 1); + + // The next multiple of PaddingValue, that's greater or equal + // to exts.extent(0) is the unique value in the range: + // [exts.extent(0), exts.extent(0) + PaddingValue) + // that is divisible by PaddingValue. + VERIFY((m.stride(1) % PaddingValue) == 0); + VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 + PaddingValue)); + } + }; +#endif + +template<typename Layout> constexpr void - test_stride_2d<std::layout_stride>() + test_stride_2d() { - std::array<int, 2> strides{13, 2}; - std::layout_stride::mapping m(std::extents<int, 3, 5>{}, strides); - VERIFY(m.stride(0) == strides[0]); - VERIFY(m.stride(1) == strides[1]); - VERIFY(m.strides() == strides); + TestStride2D<Layout>::run(); } template<typename Layout> - constexpr void - test_stride_3d(); +struct TestStride3D; template<> - constexpr void - test_stride_3d<std::layout_left>() + struct TestStride3D<std::layout_left> { - std::layout_left::mapping m(std::dextents<int, 3>(3, 5, 7)); - VERIFY(m.stride(0) == 1); - VERIFY(m.stride(1) == 3); - VERIFY(m.stride(2) == 3*5); - } + static constexpr void + run() + { + std::layout_left::mapping m(std::dextents<int, 3>(3, 5, 7)); + VERIFY(m.stride(0) == 1); + VERIFY(m.stride(1) == 3); + VERIFY(m.stride(2) == 3*5); + } + }; + template<> - constexpr void - test_stride_3d<std::layout_right>() + struct TestStride3D<std::layout_right> { - std::layout_right::mapping m(std::dextents<int, 3>(3, 5, 7)); - VERIFY(m.stride(0) == 5*7); - VERIFY(m.stride(1) == 7); - VERIFY(m.stride(2) == 1); - } + static constexpr void + run() + { + std::layout_right::mapping m(std::dextents<int, 3>(3, 5, 7)); + VERIFY(m.stride(0) == 5*7); + VERIFY(m.stride(1) == 7); + VERIFY(m.stride(2) == 1); + } + }; template<> + struct TestStride3D<std::layout_stride> + { + static constexpr void + run() + { + std::dextents<int, 3> exts(3, 5, 7); + std::array<int, 3> strides{11, 2, 41}; + std::layout_stride::mapping<std::dextents<int, 3>> m(exts, strides); + VERIFY(m.stride(0) == strides[0]); + VERIFY(m.stride(1) == strides[1]); + VERIFY(m.stride(2) == strides[2]); + VERIFY(m.strides() == strides); + } + }; + +#if __glibcxx_submdspan +template<size_t PaddingValue> + struct TestStride3D<std::layout_left_padded<PaddingValue>> + { + static constexpr void + run() + { + using Layout = std::layout_left_padded<PaddingValue>; + typename Layout::mapping<std::extents<int, 3, 5, 7>> m; + + VERIFY(m.stride(0) == 1); + + // The next multiple of PaddingValue, that's greater or equal + // to exts.extent(0) is the unique value in the range: + // [exts.extent(0), exts.extent(0) + PaddingValue) + // that is divisible by PaddingValue. + VERIFY((m.stride(1) % PaddingValue) == 0); + VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 + PaddingValue)); + + VERIFY(m.stride(1) * 5 == m.stride(2)); + } + }; +#endif + +template<typename Layout> constexpr void - test_stride_3d<std::layout_stride>() + test_stride_3d() { - std::dextents<int, 3> exts(3, 5, 7); - std::array<int, 3> strides{11, 2, 41}; - std::layout_stride::mapping<std::dextents<int, 3>> m(exts, strides); - VERIFY(m.stride(0) == strides[0]); - VERIFY(m.stride(1) == strides[1]); - VERIFY(m.stride(2) == strides[2]); - VERIFY(m.strides() == strides); + TestStride3D<Layout>::run(); } template<typename Layout> @@ -411,7 +497,8 @@ template<typename Layout> test_has_stride_0d() { using Mapping = typename Layout::mapping<std::extents<int>>; - constexpr bool expected = std::is_same_v<Layout, std::layout_stride>; + constexpr bool expected = !(std::is_same_v<Layout, std::layout_left> + || std::is_same_v<Layout, std::layout_right>); static_assert(has_stride<Mapping> == expected); } @@ -561,10 +648,27 @@ main() test_all<std::layout_left>(); test_all<std::layout_right>(); test_all<std::layout_stride>(); +#ifdef __glibcxx_submdspan + test_all<std::layout_left_padded<1>>(); + test_all<std::layout_left_padded<2>>(); + test_all<std::layout_left_padded<5>>(); +#endif test_has_op_eq<std::layout_right, std::layout_left, false>(); test_has_op_eq<std::layout_right, std::layout_stride, true>(); test_has_op_eq<std::layout_left, std::layout_stride, true>(); +#ifdef __glibcxx_submdspan + test_has_op_eq<std::layout_left, std::layout_left_padded<0>, false>(); + test_has_op_eq<std::layout_left, std::layout_left_padded<6>, false>(); + test_has_op_eq<std::layout_left, std::layout_left_padded<dyn>, false>(); + // REVIEW: The next one looks strange, because it's neither. Somehow, the + // conversion rules seem to be playing a critical role again. + // test_has_op_eq<std::layout_right, std::layout_left_padded<0>, false>(); + + test_has_op_eq<std::layout_left_padded<2>, std::layout_left_padded<6>, true>(); + test_has_op_eq<std::layout_left_padded<2>, std::layout_left_padded<dyn>, true>(); +#endif + test_has_op_eq_peculiar(); return 0; } diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc new file mode 100644 index 00000000000..6e79e191ff7 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc @@ -0,0 +1,537 @@ +// { dg-do run { target c++26 } } +#include <mdspan> + +#include <cstdint> +#include "../int_like.h" +#include <testsuite_hooks.h> + +constexpr size_t dyn = std::dynamic_extent; + +constexpr bool +test_default_ctor() +{ + using Extents = std::extents<size_t, 3, 5>;Could you also test with dynamic extents here, in particular first extent being dynamic, and de-facto ignored?+ + std::layout_left_padded<2>::mapping<Extents> msta; + VERIFY(msta.stride(0) == 1); + VERIFY(msta.stride(1) == 4); + + std::layout_left_padded<dyn>::mapping<Extents> mdyn; + VERIFY(mdyn.stride(0) == 1); + VERIFY(mdyn.stride(1) == 3); + return true; +} + +constexpr bool +test_from_exts() +{ + auto exts = std::dextents<size_t, 2>{3, 5}; + + std::layout_left_padded<0>::mapping m0_sta(exts); + VERIFY(m0_sta.stride(1) == exts.extent(0)); + + std::layout_left_padded<1>::mapping m1_sta(exts); + VERIFY(m1_sta.stride(1) == exts.extent(0)); + + std::layout_left_padded<2>::mapping m2_sta(exts); + VERIFY(m2_sta.stride(1) == 4); + + std::layout_left_padded<dyn>::mapping mdyn(exts); + VERIFY(mdyn.stride(1) == exts.extent(0)); + return true; +} + +template<size_t PaddingValue, typename CustomPadType> +constexpr bool +test_from_pad() +{ + using Layout = std::layout_left_padded<PaddingValue>; + auto pad = 3; + auto exts = std::dextents<size_t, 3>{pad + 1, 5, 7}; + typename Layout::mapping m(exts, CustomPadType{pad}); + VERIFY(std::cmp_equal(m.stride(1), 2*pad)); + VERIFY(m.extents() == exts); + return true; +} + +template<size_t PaddingValue> +constexpr void +test_from_pad_all() +{ + test_from_pad<PaddingValue, int>(); + static_assert(test_from_pad<PaddingValue, int>()); + + test_from_pad<PaddingValue, IntLike>(); + test_from_pad<PaddingValue, MutatingInt>(); + test_from_pad<PaddingValue, RValueInt>(); + + using Layout = std::layout_left_padded<PaddingValue>; + using Extents = std::dims<3>; + using Mapping = Layout::template mapping<Extents>; + static_assert(!std::is_constructible_v<Mapping, Extents, ThrowingInt>); + static_assert(!std::is_constructible_v<Mapping, Extents, NotIntLike>); +} + +constexpr bool +is_same_mapping(const auto& lhs, const auto& rhs) +{ + if (lhs.extents().rank() != rhs.extents().rank()) + return false; + + if (lhs.extents() != rhs.extents()) + return false; + + for (size_t i = 0; i < lhs.extents().rank(); ++i) + if (lhs.stride(i) != rhs.stride(i)) + return false; + return true; +} + +enum class ConversionRule +{ + Never, + Always, + Conventional +}; + +template<typename To, typename From> +consteval bool +should_convert(auto rule)Instead of passing a rule as a constant wrapper, I would prefer having a first template parameter of ConversionRule type, for this and rest of the functions,
I don't think I understand why it's not clean. It works nicely with lambdas, which is quite convenient. Additionally, we don't need totype the wordy ConversionRule as much. What do you want to achieve by avoiding constant_wrapper?
+{ + if constexpr (rule == ConversionRule::Never) + return false; + if constexpr (rule == ConversionRule::Always) + return true; + else + return std::is_convertible_v<typename From::extents_type, + typename To::extents_type>; +} + +template<typename To, typename From> + constexpr void + check_convertible(const From& m, auto conversion_rule) + { + VERIFY(is_same_mapping(m, To(m))); + constexpr bool expected = should_convert<To, From>(conversion_rule); + static_assert(std::is_convertible_v<From, To> == expected); + } + +template<typename LayoutTo, typename Esta, typename Edyn, typename Ewrong> +constexpr void +check_convertible_variants(auto msta, auto conversion_rule) +{ + using LayoutFrom = decltype(msta)::layout_type; + constexpr auto cregular = std::cw<ConversionRule::Conventional>; + + // There's a twist when both mappings are left-padded. There's two distinct + // ctors: a defaulted copy ctor and a constrained template that enables + // construction from left-padded mappings even if their layout_type is + // different. The two ctors have different rules regarding conversion. + + if constexpr (!std::same_as<LayoutTo, LayoutFrom>) + check_convertible<typename LayoutTo::mapping<Esta>>(msta, conversion_rule); + else + check_convertible<typename LayoutTo::mapping<Esta>>(msta, cregular); + + check_convertible<typename LayoutTo::mapping<Edyn>>(msta, conversion_rule); + + auto mdyn = typename LayoutFrom::mapping<Edyn>(msta); + check_convertible<typename LayoutTo::mapping<Esta>>(mdyn, conversion_rule); + if constexpr (!std::same_as<LayoutTo, LayoutFrom>) + check_convertible<typename LayoutTo::mapping<Edyn>>(mdyn, conversion_rule); + else + check_convertible<typename LayoutTo::mapping<Edyn>>(mdyn, cregular); + + static_assert(!std::is_constructible_v< + typename LayoutTo::mapping<Esta>, typename LayoutFrom::mapping<Ewrong>>); +}; + +template<typename PaddedLayout> + constexpr void + test_from_left_1d() + { + using E1 = std::extents<int, 6>; + using E2 = std::extents<int, dyn>; + using E3 = std::extents<int, 5>; + constexpr auto cr = std::cw<ConversionRule::Conventional>; + + auto msta = std::layout_left::mapping(E1{}); + check_convertible_variants<PaddedLayout, E1, E2, E3>(msta, cr); + } + +template<typename PaddedLayout> + constexpr void + test_from_left_2d() + { + using E1 = std::extents<int, 6, 5>; + using E2 = std::extents<int, dyn, 5>; + using E3 = std::extents<int, 6, 6>; + constexpr auto cr = std::cw<ConversionRule::Conventional>; + + auto msta = std::layout_left::mapping(E1{}); + check_convertible_variants<PaddedLayout, E1, E2, E3>(msta, cr); + } + +constexpr bool +test_from_left() +{ + auto check = []<typename PaddedLayout>(PaddedLayout) + { + test_from_left_1d<PaddedLayout>(); + test_from_left_2d<PaddedLayout>(); + }; + + check(std::layout_left_padded<0>{}); + check(std::layout_left_padded<1>{}); + check(std::layout_left_padded<2>{}); + check(std::layout_left_padded<6>{}); + check(std::layout_left_padded<dyn>{}); + + // rank == 1 is more permissive: + test_from_left_1d<std::layout_left_padded<5>>(); + return true; +} + +constexpr bool +test_from_stride() +{ + using E1 = std::extents<size_t, 6, 5, 7>; + using E2 = std::dims<3>; + using E3 = std::extents<size_t, 6, 6, 7>; + + auto check = []<typename PaddedLayout>(PaddedLayout) + { + auto exts = E1{}; + auto strides = std::array<int, 3>{ + 1, exts.extent(0), exts.extent(0) * exts.extent(1)}; + constexpr auto cr = std::cw<ConversionRule::Never>; + + auto m = std::layout_stride::mapping(exts, strides); + check_convertible_variants<PaddedLayout, E1, E2, E3>(m, cr); + }; + + check(std::layout_left_padded<0>{}); + check(std::layout_left_padded<1>{}); + check(std::layout_left_padded<2>{}); + check(std::layout_left_padded<6>{}); + check(std::layout_left_padded<dyn>{}); + return true; +} + +constexpr bool +test_from_leftpad_0d() +{ + using E1 = std::extents<uint16_t>; + using E2 = std::extents<uint8_t>; + using E3 = std::extents<uint8_t, 1>; + + std::layout_left_padded<6>::mapping<E1> msta{E1{}}; + + auto check = []<typename To>(To, auto m) + { + constexpr auto cr = std::cw<ConversionRule::Always>; + check_convertible_variants<To, E1, E2, E3>(m, cr); + }; + + check(std::layout_left_padded<6>{}, msta); + check(std::layout_left_padded<dyn>{}, msta); + return true; +} + +constexpr bool +test_from_leftpad_1d() +{ + using E1 = std::extents<int, 6>; + using E2 = std::extents<int, dyn>; + using E3 = std::extents<int, 6, 6>; + + std::layout_left_padded<6>::mapping<E1> msta{E1{}}; + std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}}; + + auto check = []<typename To>(To, auto m) + { + constexpr auto cr = std::cw<ConversionRule::Always>; + check_convertible_variants<To, E1, E2, E3>(m, cr); + }; + + // Remember, for rank <= 1 the padding_value is irrelevant. + check(std::layout_left_padded<6>{}, msta); + check(std::layout_left_padded<6>{}, mdyn); + check(std::layout_left_padded<dyn>{}, msta); + check(std::layout_left_padded<dyn>{}, mdyn); + return true; +} + +constexpr bool +test_from_leftpad_2d() +{ + using E1 = std::extents<int, 6, 5>; + using E2 = std::extents<int, dyn, 5>; + using E3 = std::extents<int, 6, 6>; + + std::layout_left_padded<6>::mapping<E1> msta{E1{}}; + std::layout_left_padded<dyn>::mapping<E1> mdyn{E1{}}; + + constexpr auto calways = std::cw<ConversionRule::Always>; + constexpr auto cnever = std::cw<ConversionRule::Never>; + + auto check = []<typename To>(To, auto m, auto cr) + { check_convertible_variants<To, E1, E2, E3>(m, cr); }; + + check(std::layout_left_padded<6>{}, msta, cnever); + check(std::layout_left_padded<6>{}, mdyn, cnever); + check(std::layout_left_padded<dyn>{}, msta, calways); + check(std::layout_left_padded<dyn>{}, mdyn, cnever); + return true; +} + +constexpr bool +test_from_right() +{ + using E1 = std::extents<size_t, 3>; + using E2 = std::dims<1>; + using E3 = std::extents<size_t, 5>; + constexpr auto cr = std::cw<ConversionRule::Conventional>; + + auto msta = std::layout_right::mapping(E1{}); + + // Remember, the padding_value has no effect for rank <= 1. + check_convertible_variants<std::layout_left_padded<0>, E1, E2, E3>(msta, cr); + check_convertible_variants<std::layout_left_padded<1>, E1, E2, E3>(msta, cr); + check_convertible_variants<std::layout_left_padded<2>, E1, E2, E3>(msta, cr); + check_convertible_variants<std::layout_left_padded<5>, E1, E2, E3>(msta, cr); + check_convertible_variants<std::layout_left_padded<6>, E1, E2, E3>(msta, cr); + check_convertible_variants<std::layout_left_padded<dyn>, E1, E2, E3>(msta, cr); + return true; +} + +constexpr bool +test_to_left() +{ + using E1 = std::extents<int, 6, 5>; + using E2 = std::extents<int, dyn, 5>; + using E3 = std::extents<int, 6, 6>; + + auto exts_sta = E1{}; + + auto check = [](auto msta) + { + constexpr auto cr = std::cw<ConversionRule::Conventional>; + check_convertible_variants<std::layout_left, E1, E2, E3>(msta, cr); + }; + + check(std::layout_left_padded<0>::mapping(exts_sta)); + check(std::layout_left_padded<2>::mapping(exts_sta)); + check(std::layout_left_padded<6>::mapping(exts_sta)); + check(std::layout_left_padded<dyn>::mapping(exts_sta, 0)); + check(std::layout_left_padded<dyn>::mapping(exts_sta, 2)); + check(std::layout_left_padded<dyn>::mapping(exts_sta, 6)); + return true; +} + +constexpr bool +test_never_to_right() +{ + using E1 = std::extents<size_t, 3>; + using E2 = std::dims<1>; + + auto mr_sta = std::layout_right::mapping(E1{}); + auto mlp_sta = std::layout_left_padded<2>::mapping<E1>{mr_sta}; + static_assert(!std::is_constructible_v<decltype(mr_sta), decltype(mlp_sta)>); + + auto mr_dyn = std::layout_right::mapping(E2{E1{}}); + auto mlp_dyn = std::layout_left_padded<2>::mapping<E2>{mr_dyn}; + static_assert(!std::is_constructible_v<decltype(mr_dyn), decltype(mlp_dyn)>); + return true; +} + +template<typename Layout> +constexpr void +test_strides() +{ + auto check = [](auto exts) + { + auto m = typename Layout::mapping(exts); + using IndexType = typename decltype(m)::index_type; + constexpr size_t rank = decltype(m)::extents_type::rank(); + + auto strides = m.strides(); + static_assert(std::same_as<decltype(strides), std::array<IndexType, rank>>); + VERIFY(strides.size() == rank); + for (size_t i = 0; i < strides.size(); ++i) + VERIFY(strides[i] == m.stride(i)); + }; + + check(std::extents()); + check(std::extents(0)); + check(std::extents(3)); + check(std::extents(3, 5, 7)); + check(std::extents(3, 0, 7)); +} + +constexpr bool +test_strides_all() +{ + test_strides<std::layout_left_padded<0>>(); + test_strides<std::layout_left_padded<1>>(); + test_strides<std::layout_left_padded<3>>(); + test_strides<std::layout_left_padded<dyn>>(); + return true; +} + +constexpr void +test_exhaustive_0d() +{ + auto exts = std::extents<int>{}; + + auto check = [](auto m) + { + static_assert(m.is_always_exhaustive()); + VERIFY(m.is_exhaustive()); + }; + + check(std::layout_left_padded<0>::mapping(exts)); + check(std::layout_left_padded<1>::mapping(exts)); + check(std::layout_left_padded<2>::mapping(exts)); + check(std::layout_left_padded<dyn>::mapping(exts)); +} + +constexpr void +test_exhaustive_1d() +{ + auto check_dyn_and_sta = []<typename Layout>(Layout) + { + auto check = [](auto exts) + { + auto m = typename Layout::mapping(exts); + static_assert(m.is_always_exhaustive()); + VERIFY(m.is_exhaustive()); + }; + + check(std::extents(4)); + check(std::extents<int, 4>{}); + }; + + check_dyn_and_sta(std::layout_left_padded<1>{}); + check_dyn_and_sta(std::layout_left_padded<2>{}); + check_dyn_and_sta(std::layout_left_padded<6>{}); + check_dyn_and_sta(std::layout_left_padded<dyn>{}); +} + +constexpr void +test_exhaustive_3d() +{ + auto exts_dyn = std::extents(4, 5, 7); + auto exts_sta = std::extents<int, 4, 5, 7>{}; + auto ctrue = std::cw<true>; + auto cfalse= std::cw<false>; + + auto check = [](auto m, auto static_expected, auto runtime_expected) + { + static_assert(m.is_always_exhaustive() == static_expected); + VERIFY(m.is_exhaustive() == runtime_expected); + }; + + check(std::layout_left_padded<0>::mapping(exts_sta), ctrue, true); + check(std::layout_left_padded<0>::mapping(exts_dyn), cfalse, true); + check(std::layout_left_padded<1>::mapping(exts_sta), ctrue, true); + check(std::layout_left_padded<1>::mapping(exts_dyn), cfalse, true); + check(std::layout_left_padded<2>::mapping(exts_sta), ctrue, true); + check(std::layout_left_padded<2>::mapping(exts_dyn), cfalse, true); + check(std::layout_left_padded<6>::mapping(exts_dyn), cfalse, false); + check(std::layout_left_padded<6>::mapping(exts_sta), cfalse, false); + check(std::layout_left_padded<dyn>::mapping(exts_sta), cfalse, true); + check(std::layout_left_padded<dyn>::mapping(exts_dyn, 2), cfalse, true); + check(std::layout_left_padded<dyn>::mapping(exts_dyn, 3), cfalse, false); +} + +constexpr bool +test_exhaustive() +{ + test_exhaustive_0d(); + test_exhaustive_1d(); + test_exhaustive_3d(); + return true; +} + +constexpr bool +test_op_eq() +{ + // The generic cases are handled in layouts/mapping.cc. Here we check special + // cases related to non exhaustive layouts. + using E1 = std::extents<size_t, 6, 5, 7>; + using E2 = std::dims<3>; + using E3 = std::extents<size_t, 7, 5, 7>; + + auto m1 = std::layout_left_padded<0>::mapping(E1{}); + auto m2 = std::layout_left_padded<7>::mapping(E1{}); + + VERIFY(m1 == std::layout_left_padded<0>::mapping(E1{})); + VERIFY(m1 == std::layout_left_padded<1>::mapping(E1{})); + VERIFY(m1 == std::layout_left_padded<2>::mapping(E1{})); + VERIFY(m1 == std::layout_left_padded<6>::mapping(E1{})); + VERIFY(m1 != std::layout_left_padded<7>::mapping(E1{})); + VERIFY(m1 == std::layout_left_padded<dyn>::mapping(E1{})); + VERIFY(m1 != std::layout_left_padded<dyn>::mapping(E1{}, 7)); + + VERIFY(m1 == std::layout_left_padded<0>::mapping(E2{E1{}})); + VERIFY(m1 == std::layout_left_padded<1>::mapping(E2{E1{}})); + VERIFY(m1 == std::layout_left_padded<2>::mapping(E2{E1{}})); + VERIFY(m1 == std::layout_left_padded<6>::mapping(E2{E1{}})); + VERIFY(m1 != std::layout_left_padded<7>::mapping(E2{E1{}})); + VERIFY(m1 == std::layout_left_padded<dyn>::mapping(E2{E1{}})); + VERIFY(m1 != std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7)); + + VERIFY(m2 == std::layout_left_padded<7>::mapping(E1{})); + VERIFY(m2 == std::layout_left_padded<dyn>::mapping(E1{}, 7)); + VERIFY(m2 == std::layout_left_padded<7>::mapping(E2{E1{}})); + VERIFY(m2 == std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7)); + + VERIFY(m2 != std::layout_left_padded<7>::mapping(E3{})); + VERIFY(m2 != std::layout_left_padded<dyn>::mapping(E3{}, 7)); + return true; +} + +int +main() +{ + test_default_ctor(); + static_assert(test_default_ctor()); + + test_from_exts(); + static_assert(test_from_exts()); + + test_from_left(); + static_assert(test_from_left()); + + test_from_pad_all<dyn>(); + test_from_pad_all<3>(); + + test_from_stride(); + static_assert(test_from_stride()); + + test_from_leftpad_0d(); + static_assert(test_from_leftpad_0d()); + + test_from_leftpad_1d(); + static_assert(test_from_leftpad_1d()); + + test_from_leftpad_2d(); + static_assert(test_from_leftpad_2d()); + + test_from_right(); + static_assert(test_from_right()); + + test_to_left(); + static_assert(test_to_left()); + + test_never_to_right(); + static_assert(test_never_to_right()); + + test_strides_all(); + static_assert(test_strides_all()); + + test_exhaustive(); + static_assert(test_exhaustive()); + return 0; +} diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc new file mode 100644 index 00000000000..83b4402d8c5 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc @@ -0,0 +1,225 @@ +// { dg-do compile { target c++26 } } +#include <mdspan> + +#include <cstdint> + +constexpr size_t dyn = std::dynamic_extent; + +constexpr bool +test_from_extens_oversized() +{This test does not run.+ using E1 = std::dextents<uint16_t, 2>; + using E2 = std::dextents<uint8_t, 2>; + + auto mlp = std::layout_left_padded<dyn>::mapping(E1{8, 128}); + auto m = std::layout_left_padded<dyn>::mapping<E2>(mlp); + (void) m; + return true;+}+ +constexpr bool +test_from_left() +{ + auto exts = std::extents<uint8_t, 6, dyn>{4}; + auto ml = std::layout_left::mapping(exts); + + using Layout = std::layout_left_padded<4>; + Layout::mapping<decltype(exts)> m(ml); // { dg-error "from here" } + return true; +} +static_assert(test_from_left()); + +constexpr bool +test_from_left_bad_runtime_stride() +{ + auto exts = std::extents<uint8_t, dyn, dyn>{6, 4}; + auto ml = std::layout_left::mapping(exts); + + using Layout = std::layout_left_padded<4>; + Layout::mapping<decltype(exts)> m(ml); // { dg-error "expansion of" } + return true; +} +static_assert(test_from_left_bad_runtime_stride()); + +constexpr bool +test_from_left_oversized_extents() +{ + auto exts = std::extents<uint16_t, dyn, dyn>{8, 128}; + auto ml = std::layout_left::mapping(exts); + + using Layout = std::layout_left_padded<8>; + Layout::mapping<std::extents<uint8_t, dyn, dyn>> m(ml); // { dg-error "expansion of" } + return true; +} +static_assert(test_from_left_oversized_extents()); + +template<size_t PaddingValue> + constexpr bool + test_pad_overflow() + { + auto exts = std::extents<uint8_t, dyn>{4}; + auto n = size_t(1) << 9; + auto m = typename std::layout_left_padded<PaddingValue>::mapping(exts, n); + (void) m; + return true; + } +static_assert(test_pad_overflow<1>()); // { dg-error "expansion of" } +static_assert(test_pad_overflow<dyn>()); // { dg-error "expansion of" } + +template<size_t PaddingValue> + constexpr bool + test_from_pad_negative() + { + auto exts = std::extents(4); + auto m = typename std::layout_left_padded<PaddingValue>::mapping(exts, -1); + (void) m; + return true; + } +static_assert(test_from_pad_negative<1>()); // { dg-error "expansion of" } +static_assert(test_from_pad_negative<dyn>()); // { dg-error "expansion of" } + +template<size_t Pad> + constexpr bool + test_static_pad_same() + { + using Extents = std::extents<int, dyn>; + using Mapping = typename std::layout_left_padded<Pad>::mapping<Extents>; + auto exts = Extents{4}; + auto m = Mapping(exts, Pad + 1); // { dg-error "expansion of" } + (void) m; + return true; + } +static_assert(test_static_pad_same<1>()); // { dg-error "expansion of" } +static_assert(test_static_pad_same<3>()); // { dg-error "expansion of" } + +constexpr bool +test_from_stride_wrong_stride0() +{ + using Extents = std::dextents<size_t, 2>; + auto e = Extents{3, 5}; + auto ms = std::layout_stride::mapping(e, std::array<int, 2>{2, 7}); + auto m = std::layout_left_padded<dyn>::mapping<Extents>(ms); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_stride_wrong_stride0()); + +constexpr bool +test_from_stride_wrong_stride1() +{ + using Extents = std::dextents<size_t, 2>; + auto e = Extents{3, 5}; + auto ms = std::layout_stride::mapping(e, std::array<int, 2>{1, 3}); + auto m = std::layout_left_padded<2>::mapping<Extents>(ms); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_stride_wrong_stride1()); + +constexpr bool +test_from_stride_wrong_stride2() +{ + using Extents = std::dims<3>; + auto e = Extents{3, 5, 7}; + auto ms = std::layout_stride::mapping(e, std::array<int, 3>{1, 4, 21}); + auto m = std::layout_left_padded<dyn>::mapping<Extents>(ms); // here (not implemented) + (void) m; + return true; +} +static_assert(test_from_stride_wrong_stride2()); + +constexpr bool +test_from_stride_oversized() +{ + auto exts = std::extents<uint16_t, dyn, dyn>{2, 6}; + auto ms = std::layout_stride::mapping(exts, std::array<uint16_t, 2>{1, 128}); + + using Layout = std::layout_left_padded<dyn>; + Layout::mapping<std::extents<uint8_t, dyn, dyn>> m(ms); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_stride_oversized()); + +constexpr bool +test_from_leftpad_dyn() +{ + using Extents = std::dextents<size_t, 2>; + auto e = Extents{3, 5}; + auto mlp = std::layout_left_padded<dyn>::mapping(e); + auto m = std::layout_left_padded<2>::mapping<Extents>(mlp); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_leftpad_dyn()); + +constexpr bool +test_from_leftpad_sta() +{ + using Extents = std::dextents<size_t, 2>; + auto e = Extents{3, 5}; + auto mlp = std::layout_left_padded<3>::mapping(e); + auto m = std::layout_left_padded<2>::mapping<Extents>(mlp); // { dg-error "required from" } + (void) m; + return true; +} +static_assert(test_from_leftpad_sta()); + +constexpr bool +test_from_leftpad_oversized() +{ + using E1 = std::extents<uint16_t, 8, 128>; + using E2 = std::extents<uint8_t, dyn, dyn>; + auto mlp = std::layout_left_padded<dyn>::mapping<E1>(E1{}); + auto m = std::layout_left_padded<dyn>::mapping<E2>(mlp); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_leftpad_oversized()); + +template<size_t RunId> + constexpr bool + test_to_left_not_exhaustive() + { + using E1 = std::extents<int, 6, 5>; + using E2 = std::extents<int, dyn, 5>; + + [[maybe_unused]] auto msta = std::layout_left_padded<7>::mapping(E1{}); + if constexpr (RunId == 0) + { + auto m = std::layout_left::mapping<E1>(msta); // { dg-error "required from" } + (void) m; + } + if constexpr (RunId == 1) + { + auto m = std::layout_left::mapping<E2>(msta); // { dg-error "expansion of" } + (void) m; + } + + [[maybe_unused]] auto mdyn = std::layout_left_padded<dyn>::mapping(E2{E1{}}, 7); + if constexpr (RunId == 2) + { + auto m = std::layout_left::mapping<E1>(mdyn); // { dg-error "expansion of" } + (void) m; + } + if constexpr (RunId == 3) + { + auto m = std::layout_left::mapping<E2>(mdyn); // { dg-error "expansion of" } + (void) m; + } + return true; + } +static_assert(test_to_left_not_exhaustive<0>()); +static_assert(test_to_left_not_exhaustive<1>()); +static_assert(test_to_left_not_exhaustive<2>()); +static_assert(test_to_left_not_exhaustive<3>()); + +// { dg-prune-output "padding_value must be representable as index_type" } +// { dg-prune-output "non-constant condition for static assertion" } +// { dg-prune-output "called in a constant expression" } +// { dg-prune-output "no matching function" } +// { dg-prune-output "static assertion failed" } +// { dg-prune-output "__glibcxx_assert_fail()" } +// { dg-prune-output "must be compatible with other.stride" } +// { dg-prune-output "padding_value is dynamic_extent" } +// { dg-prune-output "_S_rank <= 1" } -- 2.50.1