https://gcc.gnu.org/g:43f7452026fc05a16ce1941e14d164e7da5edb76
commit r16-7644-g43f7452026fc05a16ce1941e14d164e7da5edb76 Author: Tomasz Kamiński <[email protected]> Date: Thu Feb 19 13:14:16 2026 +0100 libstdc++: Implement mdspan::at member functions from P3383R3. This patch implements P3383R3: mdspan.at(). The mdspan::at cast only non-integral types to the index_type, before performing the checks. This allows to detect negative value of arguments, even if the index type is unsigned, and other values that would overflow index_type. libstdc++-v3/ChangeLog: * include/std/mdspan (mdspan::at, mdspan::__index_int_t): Define. * testsuite/23_containers/mdspan/at.cc: New test. Reviewed-by: Jonathan Wakely <[email protected]> Signed-off-by: Tomasz Kamiński <[email protected]> Diff: --- libstdc++-v3/include/std/mdspan | 63 ++++++++++++ libstdc++-v3/testsuite/23_containers/mdspan/at.cc | 113 ++++++++++++++++++++++ 2 files changed, 176 insertions(+) diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan index 0c89f8e71553..a6d2aadcb0cb 100644 --- a/libstdc++-v3/include/std/mdspan +++ b/libstdc++-v3/include/std/mdspan @@ -51,6 +51,9 @@ #include <tuple> #endif +#if __cplusplus > 202302L +#include <bits/stdexcept_throw.h> +#endif #ifdef __glibcxx_mdspan @@ -3083,6 +3086,62 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION operator[](const array<_OIndexType, rank()>& __indices) const { return (*this)[span<const _OIndexType, rank()>(__indices)]; } +#if __cplusplus > 202302L + template<__mdspan::__valid_index_type<index_type>... _OIndexTypes> + requires (sizeof...(_OIndexTypes) == rank()) + constexpr reference + at(_OIndexTypes... __indices) const + { + if constexpr (rank() == 0) + return _M_accessor.access(_M_handle, _M_mapping()); + else if constexpr (!(is_integral_v<_OIndexTypes> && ...)) + return at(__index_int_t<_OIndexTypes>(std::move(__indices))...); + else + { + auto __check_bound = [&]<typename _OIntType>(size_t __dim, _OIntType __index) + { + if constexpr (is_signed_v<_OIntType>) + if (__index < 0) + std::__throw_out_of_range_fmt( + __N("mdspan::at: %zuth index is negative"), __dim); + + const auto __ext = extents().extent(__dim); + if (std::cmp_greater_equal(__index, __ext)) + std::__throw_out_of_range_fmt( + __N("mdspan::at: %zuth index (which is %zu)" + " >= extent(%zu) (which is %zu)"), + __dim, size_t(__index), __dim, size_t(__ext)); + }; + auto __check_bounds = [&]<size_t... _Counts>(index_sequence<_Counts...>) + { (__check_bound(_Counts, __indices), ...); }; + + __check_bounds(make_index_sequence<rank()>()); + auto __index = _M_mapping(static_cast<index_type>(__indices)...); + return _M_accessor.access(_M_handle, __index); + } + } + + template<typename _OIndexType> + requires __mdspan::__valid_index_type<const _OIndexType&, index_type> + constexpr reference + at(span<_OIndexType, rank()> __indices) const + { + auto __call = [&]<size_t... _Counts>(index_sequence<_Counts...>) + -> reference + { + return at( + __index_int_t<_OIndexType>(as_const(__indices[_Counts]))...); + }; + return __call(make_index_sequence<rank()>()); + } + + template<typename _OIndexType> + requires __mdspan::__valid_index_type<const _OIndexType&, index_type> + constexpr reference + at(const array<_OIndexType, rank()>& __indices) const + { return at(span<const _OIndexType, rank()>(__indices)); } +#endif // C++26 + constexpr size_type size() const noexcept { @@ -3150,6 +3209,10 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION stride(rank_type __r) const { return _M_mapping.stride(__r); } private: + template<typename _OIndexType> + using __index_int_t = std::__conditional_t< + is_integral_v<_OIndexType>, _OIndexType, index_type>; + [[no_unique_address]] accessor_type _M_accessor = accessor_type(); [[no_unique_address]] mapping_type _M_mapping = mapping_type(); [[no_unique_address]] data_handle_type _M_handle = data_handle_type(); diff --git a/libstdc++-v3/testsuite/23_containers/mdspan/at.cc b/libstdc++-v3/testsuite/23_containers/mdspan/at.cc new file mode 100644 index 000000000000..4e659f572754 --- /dev/null +++ b/libstdc++-v3/testsuite/23_containers/mdspan/at.cc @@ -0,0 +1,113 @@ +// { dg-do run { target c++26 } } +#include <mdspan> + +#include <testsuite_hooks.h> +#include "int_like.h" +#include <stdexcept> + +template<typename MDSpan, typename... Args> +concept valid_at = requires (MDSpan md, Args... args) +{ + { md.at(args...) } -> std::same_as<typename MDSpan::reference>; +}; + +template<typename Int, bool ValidForPacks, bool ValidForArrays> + constexpr bool + test_at() + { + using Extents = std::extents<int, 3, 5, 7>; + auto exts = Extents{}; + + auto mapping = std::layout_left::mapping(exts); + constexpr size_t n = mapping.required_span_size(); + std::array<double, n> storage{}; + + auto md = std::mdspan(storage.data(), mapping); + using MDSpan = decltype(md); + + for(int i = 0; i < exts.extent(0); ++i) + for(int j = 0; j < exts.extent(1); ++j) + for(int k = 0; k < exts.extent(2); ++k) + { + storage[mapping(i, j, k)] = 1.0; + if constexpr (ValidForPacks) + VERIFY(md.at(Int(i), Int(j), Int(k)) == 1.0); + + if constexpr (ValidForArrays) + { + std::array<Int, 3> ijk{Int(i), Int(j), Int(k)}; + VERIFY(md.at(ijk) == 1.0); + VERIFY(md.at(std::span(ijk)) == 1.0); + } + storage[mapping(i, j, k)] = 0.0; + } + + if constexpr (!ValidForPacks) + static_assert(!valid_at<MDSpan, Int, int, Int>); + + if constexpr (!ValidForArrays) + { + static_assert(!valid_at<MDSpan, std::array<Int, 3>>); + static_assert(!valid_at<MDSpan, std::span<Int, 3>>); + } + + auto verify_throw = [&md](int i, int j, int k) + { + if constexpr (ValidForPacks) + try + { + md.at(Int(i), Int(j), Int(k)); + VERIFY(false); + } + catch (std::out_of_range&) + { + VERIFY(true); + } + + if constexpr (ValidForArrays) + { + std::array<Int, 3> ijk{Int(i), Int(j), Int(k)}; + try + { + md.at(ijk); + VERIFY(false); + } + catch (std::out_of_range&) + { + VERIFY(true); + } + + try + { + md.at(std::span(ijk)); + VERIFY(false); + } + catch (std::out_of_range&) + { + VERIFY(true); + } + } + }; + + verify_throw(-1, 0, 0); + verify_throw(0, -3, 0); + verify_throw(0, 0, -5); + + verify_throw(11, 0, 0); + verify_throw(0, 13, 0); + verify_throw(0, 0, 15); + + return true; + } + +int +main() +{ + test_at<int, true, true>(); + static_assert(test_at<int, true, true>()); + test_at<short, true, true>(); + test_at<IntLike, true, true>(); + test_at<ThrowingInt, false, false>(); + test_at<MutatingInt, true, false>(); + test_at<RValueInt, true, false>(); +}
