On Mon, 23 Feb 2026 at 08:14, Tomasz Kamiński <[email protected]> wrote: > > 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.
Nice. > > libstdc++-v3/ChangeLog: > > * include/std/mdspan (mdspan::at, mdspan::__index_int_t): > Define. > * testsuite/23_containers/mdspan/at.cc: New test. > > Signed-off-by: Tomasz Kamiński <[email protected]> > --- > Tested on x86_64-linux. OK for trunk? OK with one change noted below, for conditional_t. > > libstdc++-v3/include/std/mdspan | 63 ++++++++++ > .../testsuite/23_containers/mdspan/at.cc | 113 ++++++++++++++++++ > 2 files changed, 176 insertions(+) > create mode 100644 libstdc++-v3/testsuite/23_containers/mdspan/at.cc > > diff --git a/libstdc++-v3/include/std/mdspan b/libstdc++-v3/include/std/mdspan > index 0c89f8e7155..3476d42a129 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>; std::__conditional_t is cheaper to instantiate than std::conditional_t > + > [[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 00000000000..4e659f57275 > --- /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>(); > +} > -- > 2.53.0 >
