On 9/11/25 16:07, Tomasz Kaminski wrote:
On Thu, Sep 11, 2025 at 1:46 PM Luc Grosheintz <[email protected]> wrote:This commit adds a new layout layout_left_padded as standardized in N5014 but with one deviation. It includes checking of all mandates and prerequisites. It adds a purely internal feature testing macro padded_layouts 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 (padded_layouts): 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/class_mandate_neg.cc: Add tests for layout_left_padded. * testsuite/23_containers/mdspan/layouts/ctors.cc: Ditto. * 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: Ditto. * testsuite/23_containers/mdspan/layouts/padded_neg.cc: Ditto. Signed-off-by: Luc Grosheintz <[email protected]>Below is only a partial review of the changes.--- libstdc++-v3/include/bits/version.def | 10 + libstdc++-v3/include/bits/version.h | 9 + libstdc++-v3/include/std/mdspan | 522 ++++++++++++++- libstdc++-v3/src/c++23/std.cc.in | 8 +- .../mdspan/layouts/class_mandate_neg.cc | 1 + .../23_containers/mdspan/layouts/ctors.cc | 61 +- .../mdspan/layouts/debug/padded_neg.cc | 22 + .../23_containers/mdspan/layouts/empty.cc | 12 +- .../23_containers/mdspan/layouts/mapping.cc | 72 ++- .../23_containers/mdspan/layouts/padded.cc | 611 ++++++++++++++++++ .../mdspan/layouts/padded_neg.cc | 280 ++++++++ 11 files changed, 1592 insertions(+), 16 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..63e5f394a8f 100644 --- a/libstdc++-v3/include/bits/version.def +++ b/libstdc++-v3/include/bits/version.def @@ -1050,6 +1050,16 @@ ftms = { cxxmin = 26; extra_cond = "__glibcxx_assume_aligned " "&& __glibcxx_is_sufficiently_aligned"; + }; +}; + +// Unofficial macro signaling presence of the two padded layouts. +ftms = { + name = padded_layouts; + no_stdname = true; // Don't change: it's not a standardized FTM. + values = { + v = 1; + cxxmin = 26; }; }; diff --git a/libstdc++-v3/include/bits/version.h b/libstdc++-v3/include/bits/version.h index b05249857d2..6b4197e599d 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_padded_layouts) +# if (__cplusplus > 202302L) +# define __glibcxx_padded_layouts 1L +# if defined(__glibcxx_want_all) || defined(__glibcxx_want_padded_layouts) +# endif +# endif +#endif /* !defined(__cpp_lib_padded_layouts) && defined(__glibcxx_want_padded_layouts) */ +#undef __glibcxx_want_padded_layouts + #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 9dd0c85f5b0..251566c7051 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -233,6 +233,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION _S_static_extents() noexcept { return _Extents; } + static constexpr span<const size_t> + _S_static_extents(size_t __begin, size_t __end) noexcept + { return span(_Extents.data() + __begin, __end - __begin); } + constexpr span<const _IndexType> _M_dynamic_extents(size_t __begin, size_t __end) const noexcept requires (_Extents.size() > 0) @@ -261,6 +265,11 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION __static_extents() noexcept { return _Extents::_Storage::_S_static_extents(); } + template<typename _Extents> + constexpr span<const size_t> + __static_extents(size_t __begin, size_t __end) noexcept + { return _Extents::_Storage::_S_static_extents(__begin, __end); }Instead of befriending this function, and adding new member of extents, I would just implement it as: __static_extents<_Extents>().subspan(__begin, __end). Also I would add default argument for __end to be equal __Extent::rank();
Yes, neat.
+ // Pre-compute: \prod_{i = 0}^r _Extents[i], for r = 0,..., n (exclusive) template<array _Extents> constexpr auto __fwd_partial_prods = [] consteval @@ -420,6 +429,9 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION friend const array<size_t, rank()>& __mdspan::__static_extents<extents>(); + friend span<const size_t> + __mdspan::__static_extents<extents>(size_t __begin, size_t __end);Then remove this friend.+ friend span<const index_type> __mdspan::__dynamic_extents<extents>(const extents&, size_t, size_t); @@ -476,6 +488,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 + { + 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 +594,22 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION class mapping; }; +#ifdef __glibcxx_padded_layouts + 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> @@ -669,10 +712,39 @@ _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 = requiresI have suggested to replace this by: __is_mapping_of< _Layout<_Mapping::padding_value>, _Mapping>>; Didn't it work?
Sorry, I missed that.
+ { + 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_padded_layouts + 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>; +#endif + + template<typename _PaddedMapping> + consteval size_t + __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> +#ifdef __glibcxx_padded_layouts + || __is_left_padded_mapping<_Mapping> + || __is_right_padded_mapping<_Mapping> +#endif + ; // A tag type to create internal ctors. class __internal_ctor @@ -726,6 +798,30 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION : mapping(__other.extents(), __mdspan::__internal_ctor{}) { __glibcxx_assert(*this == __other); } +#if __glibcxx_padded_layouts + 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)); + } +#endif + constexpr mapping& operator=(const mapping&) noexcept = default; @@ -1173,6 +1269,430 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION [[no_unique_address]] _Strides _M_strides; }; +#ifdef __glibcxx_padded_layouts + namespace __mdspan + { + constexpr size_t + __least_multiple_at_least(size_t __x, size_t __y)I would name it just __least_multiple, the current name makes usage very wordy.+ { + if (__x <= 1) + return __y; + return (__y / __x + (__y % __x != 0)) * __x ; + } + + template<typename _IndexType>+ constexpr bool+ __is_representable_least_multiple(size_t __x, size_t __y) + {+ constexpr auto __y_max = numeric_limits<_IndexType>::max();+ if(std::cmp_greater(__y, __y_max))+ return false; + + if(__x <= 1) + return true; + + auto __max_delta = __y_max - static_cast<_IndexType>(__y); + auto __delta = (__y % __x == 0) ? size_t(0) : (__x - (__y % __x));Store __y % __x in variable, instead of repeating it.
For readability sake? Something like __y_mod_x. Somehow I can't imagine it matters for speed. It seems like an obvious common subexpression that should be eliminated reliably.
+ return std::cmp_less_equal(__delta, __max_delta); + } + + template<size_t _PaddingValue, typename _Extents, typename _IndexType> + concept __valid_static_stride = (_Extents::rank() <= 1) + || (_PaddingValue == dynamic_extent) + || (_Extents::static_extent(0) == dynamic_extent) + || (__is_representable_least_multiple<_IndexType>( + _PaddingValue, _Extents::static_extent(0))); + + template<typename _IndexType, size_t _StaticStride1, size_t _Extent0, size_t..._Extents> + constexpr auto __make_left_padded_extent( + extents<_IndexType, _StaticStride1> __stride1, + const extents<_IndexType, _Extent0, _Extents...>& __exts) + { + auto __impl = [&]<size_t... _Indices>(integer_sequence<size_t, _Indices...>) + { + return extents<_IndexType, _StaticStride1, _Extents...>{ + __stride1.extent(0), __exts.extent(_Indices + 1)...}; + }; + return __impl(make_index_sequence<sizeof...(_Extents)>()); + } + + template<typename _Stride, typename _Extents> + constexpr bool + __is_representable_left_padded_size(const _Stride& __stride1, + const _Extents& __exts) + { + return __is_representable_extents( + __make_left_padded_extent(__stride1, __exts)); + } + + template<size_t _PaddingValue, typename _Extents, typename _TargetInt> + concept __valid_leftpad_size = (_Extents::rank() <= 1) + || (_PaddingValue == dynamic_extent) + || (!__all_static(__static_extents<_Extents>())) + || (__contains_zero(__static_extents<_Extents>())) + || (__static_quotient(__static_extents<_Extents>(1, _Extents::rank()), + numeric_limits<_TargetInt>::max() / __least_multiple_at_least( + _PaddingValue, _Extents::static_extent(0))) != 0); + + 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"); + + static_assert(__mdspan::__valid_static_stride<padding_value, extents_type, + index_type>, + "stride(1) must be representable as index_type"); + + static_assert(__mdspan::__valid_static_stride<padding_value, extents_type, + size_t>, + "stride(1) must be representable as size_t");After checking this one, we can compute _S_static_stride without overflow, so the __mdspan::__valid_static_stride<padding_value, extents_type, index_type> could be expressed as _S_static_stride <= numeric_limits<index_type>::max(), which would be my preference. This way __is_representable_least_multiple can use size_t.
Okay, understood, I'll change it.
+ + static_assert(__mdspan::__valid_leftpad_size<padding_value, extents_type, index_type>); + static_assert(__mdspan::__valid_leftpad_size<padding_value, extents_type, size_t>);This does not seem to be required by standard, only for index_type. Again we could use _S_static_stride.
I think I'm missing something, because to me it reads like it does:
then the product of LEAST-MULTIPLE-AT-LEAST(...) and all values
ext.static_extent(k) ... is representable as a value of type size_t, and is representable as a value of type index_type. https://eel.is/c++draft/views#mdspan.layout.leftpad.overview-5.4 I agree we can't handle cases where index_type is larger than size_t; so in that sense, it's redundant. But it's part of the standard AFAICT.
+ + // 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) + || (cmp_less_equal(padding_value, + numeric_limits<index_type>::max())), + "padding_value must be representable as index_type"); + + 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);We should add condition here: else if constexpr (_Extents::static_extent(0) == 0) return size_t(0); This is exposition only in standard, so I think we can do it.
I can certainly do that, but this is a consteval function. There's no runtime overhead; and the more if constexpr branches we have the more we suffer from combinational explotion of variations and therefore tests. Sooner or later we will miss one. I'm very reluctant about this proposal; but naturally happy to make the change.
+ else if constexpr (padding_value == dynamic_extent + || _Extents::static_extent(0) == dynamic_extent) + return dynamic_extent; + else + return __mdspan::__least_multiple_at_least( + padding_value, _Extents::static_extent(0)); + }(); + + consteval 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));Static checks already cover this.
but what if `_M_exents is completely dynamic, e.g.
std::extents<uint8_t, dyn, dyn>{128, 4}
+ if constexpr (_S_rank > 1)After thinking about it, I think that this function can be implemented as: if constexpr (_S_rank <= 0) return; // stride is not used else if constexpr (__exts.static_extent(0) == dynamic_extent) return; // extent(0) is dynamic so store zero else if constexpr (_PaddingValue != dynamic_extent) return; // everything was know statically, and we statically checked the sizes in mandates else // if all extents are static, we already checked the size, otherwise total size is zero _M_stride = _Stride{__ext.static_extent(0)}; And because we don't have any preconditions, I would make this function noexcept.
Sorry, I'm lost. This ctor has three preconditions: https://eel.is/c++draft/views#mdspan.layout.leftpad.cons-2
+ { + if constexpr (padding_value != dynamic_extent) + __glibcxx_assert( + __mdspan::__is_representable_least_multiple<index_type>( + padding_value, _M_extents.extent(0))); + + index_type __stride; + if constexpr (padding_value == dynamic_extent) + __stride = __exts.extent(0); + else if constexpr (__exts.static_extent(0) != dynamic_extent) + return; + else + { + __stride = static_cast<index_type>( + __mdspan::__least_multiple_at_least(padding_value, + __exts.extent(0))); + __glibcxx_assert( + __mdspan::__is_representable_left_padded_size( + std::dextents<index_type, 1>{__stride}, + _M_extents)); + } + _M_stride = _Stride{__stride}; + } + } + + 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 && _S_static_stride == dynamic_extent) + { + __glibcxx_assert( + __mdspan::__is_representable_least_multiple<index_type>( + __pad, _M_extents.extent(0))); + + _M_stride = _Stride{static_cast<index_type>( + __mdspan::__least_multiple_at_least( + __pad, _M_extents.extent(0)))}; + + __glibcxx_assert( + __mdspan::__is_representable_left_padded_size( + _M_stride, _M_extents)); + } + } + + 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)"); + + __glibcxx_assert((!(_S_rank > 1 && padding_value != dynamic_extent)) + || (std::cmp_equal(_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(padding_value, + __other.extents().extent(0)))); + + 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 = _Stride{__other.stride(1)}; + + if constexpr (_S_rank > 2)Could we just compare *this with others? Or after casting it to layout_stride? This will remove also check for stride(0). Otherwise, I think I would simply drop this assertion.
Ofc, that looks like and easy option. I chose not to because I feared that it would be rejected because it needlessly compares strides 0, and 1.
+ { + _GLIBCXX_DEBUG_ASSERT([&]() + { + for (size_t __i = 2; __i < _S_rank; ++__i) + if (stride(__i) != __other.stride(__i)) + return false; + return true; + }()); + } + } + + 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(padding_value,+ __other.extents().extent(0))))I think this should be: if constexpr (_S_static_stride != dynamic_extent) assert(cmp_equal(__other.stride(1), _S_static_stride)); else if constexpr (padding_value != dynamic_extent) assert(cmp_equal(__other.stride(1), __mdspan::__least_multiple_at_least(padding_value, _other.extents().extent(0))));
Ah, indeed I forgot to change it.
+ && "other.stride(1) must be the next larger multiple of " + "padding_value that is greater or equal to other.extent(0)"); + } + __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 = _Stride{__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)We need to compute this in unsigned tupe, in case if multiplication overlow, but later correction puts value in correct range.
Indeed, it unfortunate, because it looked like the mandates were developer friendly, e.g.: https://eel.is/c++draft/views#mdspan.layout.leftpad.overview-5.4 https://eel.is/c++draft/views#mdspan.layout.leftpad.cons-1.3 https://eel.is/c++draft/views#mdspan.layout.leftpad.cons-4.4 https://eel.is/c++draft/views#mdspan.layout.leftpad.cons-8.2 the above all allow us to compute the product safely; but then this ctor prevents us from assuming that it's safe to compute the products: https://eel.is/c++draft/views#mdspan.layout.leftpad.cons-11.4 Thank you, nicely spotted.
+ - (_M_stride.extent(0) - _M_extents.extent(0)); + } + + // _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 || cmp_equal(__self.stride(1), + __other.stride(1))); + } + + private: + using _Stride = std::extents<index_type, _S_static_stride>; + [[no_unique_address]] _Stride _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..8b2f4b24c9c 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_padded_layouts + 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/class_mandate_neg.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc index 7091153daba..edf07c983da 100644 --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/class_mandate_neg.cc @@ -46,3 +46,4 @@ auto b6 = B<6, std::layout_stride, std::layout_left>(); // { dg-error "require auto b7 = B<7, std::layout_stride, std::layout_stride>(); // { dg-error "required from" } // { dg-prune-output "must be representable as index_type" } +// { dg-prune-output "static assertion failed" } diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/ctors.cc index 23c0a55dae1..54f79fcd6ec 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_padded_layouts +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>) + 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_padded_layouts + 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..189becfb92f --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/debug/padded_neg.cc @@ -0,0 +1,22 @@ +// { dg-do run { target c++26 xfail *-*-* } } +// { dg-require-debug-mode "" } + +#include <mdspan> +#include <array> + +void +test_from_strided_mismatching_strides() +{ + auto exts = std::extents(3, 5, 7); + auto strides = std::array<size_t, 3>{1, 4, 21 /* != 20 */}; + auto ms = std::layout_stride::mapping(exts, strides); + auto m = std::layout_left_padded<2>::mapping<std::dims<3>>(ms); + (void) m; +} + +int +main() +{ + test_from_strided_mismatching_strides(); + return 0; +}; diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/empty.cc index 655b9b6d6c3..123e3c61deb 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,11 @@ main() static_assert(test_all<std::layout_left>()); static_assert(test_all<std::layout_right>()); static_assert(test_all<std::layout_stride>()); +#ifdef __glibcxx_padded_layouts + static_assert(test_all<std::layout_left_padded<0>>()); + static_assert(test_all<std::layout_left_padded<1>>()); + static_assert(test_all<std::layout_left_padded<2>>()); + static_assert(test_all<std::layout_left_padded<dyn>>()); +#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 db15e2a48f3..91d66ea87b8 100644 --- a/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/mapping.cc @@ -370,6 +370,30 @@ template<> } }; +#if __glibcxx_padded_layouts +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; + size_t effective_pad = (PaddingValue == 0 || PaddingValue == dyn) + ? size_t(1) : PaddingValue; + + 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) % effective_pad) == 0); + VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 + effective_pad)); + } + }; +#endif + template<typename Layout> constexpr void test_stride_2d() @@ -423,6 +447,32 @@ template<> } }; +#if __glibcxx_padded_layouts +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; + size_t effective_pad = (PaddingValue == 0 || PaddingValue == dyn) + ? size_t(1) : PaddingValue; + + 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) % effective_pad) == 0); + VERIFY(3 <= m.stride(1) && std::cmp_less(m.stride(1), 3 + effective_pad)); + + VERIFY(m.stride(1) * 5 == m.stride(2)); + } + }; +#endif + template<typename Layout> constexpr void test_stride_3d() @@ -451,7 +501,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); } @@ -601,10 +652,29 @@ main() test_all<std::layout_left>(); test_all<std::layout_right>(); test_all<std::layout_stride>(); +#ifdef __glibcxx_padded_layouts + 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<5>>(); + test_all<std::layout_left_padded<dyn>>(); +#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_padded_layouts + 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>(); + // 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..a0cd30c39ff --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded.cc @@ -0,0 +1,611 @@ +// { dg-do run { target c++26 } } +#include <mdspan> + +#include <cstdint> +#include "../int_like.h" +#include <testsuite_hooks.h> + +#include <iostream> + +constexpr size_t dyn = std::dynamic_extent; + +constexpr bool +test_representable_least_multiple() +{ + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(0, 0));I would prefer if we would not use the internals in implementation here, and rely on negative tests for combination of stride and mapping. It was very beneficial for us to run libc++ tests on our implementation, and this test is implementation specific if they would like to do the same.
I can remove them again.
+ VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(1, 0)); + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(0, 255)); + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(1, 255)); + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(2, 2)); + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(5, 254)); + VERIFY(std::__mdspan::__is_representable_least_multiple<uint8_t>(5, 255)); + VERIFY(!std::__mdspan::__is_representable_least_multiple<uint8_t>(2, 255)); + VERIFY(!std::__mdspan::__is_representable_least_multiple<uint8_t>(128, 129)); + return true; +} + +constexpr bool +test_representable_padded_size() +{ + { + using E = std::extents<uint8_t, 64, 2>; + [[maybe_unused]] std::layout_left_padded<1>::mapping<E> m1; + } + + { + using E = std::extents<uint8_t, 0, 2>; + [[maybe_unused]] std::layout_left_padded<0>::mapping<E> m1; + [[maybe_unused]] std::layout_left_padded<1>::mapping<E> m2; + [[maybe_unused]] std::layout_left_padded<128>::mapping<E> m3; + [[maybe_unused]] std::layout_left_padded<255>::mapping<E> m4; + } + + { + using E = std::extents<uint8_t, 0, 2>; + [[maybe_unused]] std::layout_left_padded<dyn>::mapping<E> m1(E{}, 128); + [[maybe_unused]] std::layout_left_padded<dyn>::mapping<E> m2(E{}, 0); + [[maybe_unused]] std::layout_left_padded<dyn>::mapping<E> m3(E{}, 1); + [[maybe_unused]] std::layout_left_padded<dyn>::mapping<E> m4(E{}, 128); + } + + { + using E = std::extents<uint8_t, dyn, 2>; + [[maybe_unused]] std::layout_left_padded<0>::mapping<E> m1; + [[maybe_unused]] std::layout_left_padded<1>::mapping<E> m2; + [[maybe_unused]] std::layout_left_padded<128>::mapping<E> m3; + [[maybe_unused]] std::layout_left_padded<255>::mapping<E> m4; + } + return true; +} + +constexpr bool +test_default_ctor() +{ + { + using E = std::extents<size_t, 3, 5>; + + std::layout_left_padded<2>::mapping<E> msta; + VERIFY(msta.stride(0) == 1); + VERIFY(msta.stride(1) == 4); + + std::layout_left_padded<dyn>::mapping<E> mdyn; + VERIFY(mdyn.stride(0) == 1); + VERIFY(mdyn.stride(1) == 3); + } + + { + using E = std::extents<size_t, dyn, 5>; + + std::layout_left_padded<2>::mapping<E> msta; + VERIFY(msta.stride(0) == 1); + VERIFY(msta.stride(1) == 0); + + std::layout_left_padded<dyn>::mapping<E> mdyn; + VERIFY(mdyn.stride(0) == 1); + VERIFY(mdyn.stride(1) == 0); + } + 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, + Regular +}; + +template<typename To, typename From> +consteval bool +should_convert(auto rule) +{ + 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::Regular>; + + // 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::Regular>; + + 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::Regular>; + + 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 void +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); +} + +constexpr void +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); +} + +constexpr void +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); +} + +constexpr bool +test_from_leftpad() +{ + test_from_leftpad_0d(); + test_from_leftpad_1d(); + test_from_leftpad_2d(); + 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::Regular>; + + 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::Regular>; + 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_representable_least_multiple(); + static_assert(test_representable_least_multiple()); + + test_representable_padded_size(); + static_assert(test_representable_padded_size()); + + 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(); + static_assert(test_from_leftpad()); + + 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()); + + test_op_eq(); + static_assert(test_op_eq()); + 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..72ef55ea87b --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/layouts/padded_neg.cc @@ -0,0 +1,280 @@ +// { dg-do compile { target c++26 } } +#include <mdspan> + +#include <cstdint> + +constexpr size_t dyn = std::dynamic_extent; + +constexpr bool +test_from_extens_representable_sta() +{ + using E1 = std::extents<uint8_t, 8, 128>; + auto m = std::layout_left_padded<dyn>::mapping(E1{}); // { dg-error "from here" } + return true; +} +static_assert(test_from_extens_representable_sta()); + +constexpr bool +test_from_extents_representable_padded_size() +{ + using E1 = std::extents<uint8_t, 8, 128>; + using E2 = std::dextents<uint8_t, 2>; + + auto m = std::layout_left_padded<dyn>::mapping(E2{E1{}}); // { dg-error "expansion of" } + return true; +} +static_assert(test_from_extents_representable_padded_size()); + +constexpr bool +test_from_extents_representable_stride() +{ + using E1 = std::extents<uint8_t, dyn, 1>; + auto m = std::layout_left_padded<128>::mapping(E1{129}); // { dg-error "expansion of" }+ return true;+} +static_assert(test_from_extents_representable_stride()); + +constexpr bool +test_from_pad_representable_stride() +{ + using E1 = std::dextents<uint8_t, 2>; + auto m = std::layout_left_padded<dyn>::mapping(E1{129, 2}, 128); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_pad_representable_stride()); + +constexpr bool +test_from_pad_representable_padded_size() +{ + using E1 = std::dextents<uint8_t, 2>; + auto m = std::layout_left_padded<dyn>::mapping(E1{64, 2}, 128); // { dg-error "expansion of" } + (void) m; + return true; +} +static_assert(test_from_pad_representable_padded_size()); + +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_representable_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_representable_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>()); + +constexpr bool +test_statically_bad_padding_value() +{ + using Extents = std::extents<uint8_t, 129, 2>; + std::layout_left_padded<2>::mapping<Extents> m; // { dg-error "required from here" } + return true; +} +static_assert(test_statically_bad_padding_value()); + +constexpr bool +test_statically_oversized() +{ + using Extents = std::extents<uint8_t, 64, 2>; + std::layout_left_padded<128>::mapping<Extents> m; // { dg-error "required from here" } + return true; +} +static_assert(test_statically_oversized()); + +// { 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
