On Thu, Jul 3, 2025 at 12:38 PM Luc Grosheintz <luc.groshei...@gmail.com> wrote:
> This commit completes the implementation of P2897R7 by implementing and > testing the template class aligned_accessor. > > libstdc++-v3/ChangeLog: > > * include/bits/version.def (aligned_accessor): Add. > * include/bits/version.h: Regenerate. > * include/std/mdspan (aligned_accessor): New class. > * src/c++23/std.cc.in (aligned_accessor): Add. > * testsuite/23_containers/mdspan/accessors/generic.cc: Add tests > for aligned_accessor. > * testsuite/23_containers/mdspan/accessors/aligned.cc: New test. > * testsuite/23_containers/mdspan/accessors/aligned_ftm.cc: New > test. > * testsuite/23_containers/mdspan/accessors/aligned_neg.cc: New > test. > --- > libstdc++-v3/include/bits/version.def | 10 +++ > libstdc++-v3/include/bits/version.h | 10 +++ > libstdc++-v3/include/std/mdspan | 72 +++++++++++++++++++ > libstdc++-v3/src/c++23/std.cc.in | 3 +- > .../23_containers/mdspan/accessors/aligned.cc | 43 +++++++++++ > .../mdspan/accessors/aligned_ftm.cc | 6 ++ > .../mdspan/accessors/aligned_neg.cc | 33 +++++++++ > .../accessors/debug/aligned_access_neg.cc | 23 ++++++ > .../accessors/debug/aligned_offset_neg.cc | 23 ++++++ > .../23_containers/mdspan/accessors/generic.cc | 27 +++++++ > 10 files changed, 249 insertions(+), 1 deletion(-) > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_ftm.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_neg.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_access_neg.cc > create mode 100644 > libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_offset_neg.cc > > diff --git a/libstdc++-v3/include/bits/version.def > b/libstdc++-v3/include/bits/version.def > index a2695e67716..42445165b91 100644 > --- a/libstdc++-v3/include/bits/version.def > +++ b/libstdc++-v3/include/bits/version.def > @@ -1022,6 +1022,16 @@ ftms = { > }; > }; > > +ftms = { > + name = aligned_accessor; > + values = { > + v = 202411; > + cxxmin = 26; > + extra_cond = "__glibcxx_assume_aligned " > + "&& __glibcxx_is_sufficiently_aligned"; > + }; > +}; > + > ftms = { > name = ssize; > values = { > diff --git a/libstdc++-v3/include/bits/version.h > b/libstdc++-v3/include/bits/version.h > index 1b17a965239..3efa7b1baae 100644 > --- a/libstdc++-v3/include/bits/version.h > +++ b/libstdc++-v3/include/bits/version.h > @@ -1143,6 +1143,16 @@ > #endif /* !defined(__cpp_lib_mdspan) && defined(__glibcxx_want_mdspan) */ > #undef __glibcxx_want_mdspan > > +#if !defined(__cpp_lib_aligned_accessor) > +# if (__cplusplus > 202302L) && (__glibcxx_assume_aligned && > __glibcxx_is_sufficiently_aligned) > +# define __glibcxx_aligned_accessor 202411L > +# if defined(__glibcxx_want_all) || > defined(__glibcxx_want_aligned_accessor) > +# define __cpp_lib_aligned_accessor 202411L > +# endif > +# endif > +#endif /* !defined(__cpp_lib_aligned_accessor) && > defined(__glibcxx_want_aligned_accessor) */ > +#undef __glibcxx_want_aligned_accessor > + > #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 c72a64094b7..6eb804bf9a0 100644 > --- a/libstdc++-v3/include/std/mdspan > +++ b/libstdc++-v3/include/std/mdspan > @@ -39,7 +39,12 @@ > #include <limits> > #include <utility> > > +#if __cplusplus > 202302L > +#include <bits/align.h> > +#endif > + > #define __glibcxx_want_mdspan > +#define __glibcxx_want_aligned_accessor > #include <bits/version.h> > > #ifdef __glibcxx_mdspan > @@ -1035,6 +1040,73 @@ _GLIBCXX_BEGIN_NAMESPACE_VERSION > { return __p + __i; } > }; > > +#ifdef __glibcxx_aligned_accessor > + template<typename _ElementType, size_t _ByteAlignment> > + struct aligned_accessor > + { > + static_assert(has_single_bit(_ByteAlignment), > + "ByteAlignment must be a power of two"); > + static_assert(_ByteAlignment >= alignof(_ElementType), > + "ByteAlignment is too small for ElementType"); > + static_assert(!is_array_v<_ElementType>, > + "ElementType must not be an array type"); > + static_assert(!is_abstract_v<_ElementType>, > + "ElementType must not be an abstract class type"); > + > + using offset_policy = default_accessor<_ElementType>; > + using element_type = _ElementType; > + using reference = element_type&; > + using data_handle_type = element_type*; > + > + static constexpr size_t byte_alignment = _ByteAlignment; > + > + constexpr > + aligned_accessor() noexcept = default; > + > + template<typename _OElementType, size_t _OByteAlignment> > + requires (is_convertible_v<_OElementType(*)[], element_type(*)[]> > + && _OByteAlignment >= byte_alignment) > + constexpr > + aligned_accessor(aligned_accessor<_OElementType, _OByteAlignment>) > + noexcept > + { } > + > + template<typename _OElementType> > + requires is_convertible_v<_OElementType(*)[], element_type(*)[]> > + constexpr explicit > + aligned_accessor(default_accessor<_OElementType>) noexcept > + { } > + > + template<typename _OElementType> > + requires is_convertible_v<_OElementType(*)[], element_type(*)[]> > + constexpr > + operator default_accessor<_OElementType>() const noexcept > + { return {}; } > + > + constexpr reference > + access(data_handle_type __p, size_t __i) const noexcept > + { > + if !consteval > + { > + _GLIBCXX_DEBUG_ASSERT( > + std::is_sufficiently_aligned<_ByteAlignment>(__p)); > + } > I would not add this check, as the purpose of this accessor is to assume aligned, and that should be checked during mdspan construction, however we do not currently have a way to do so. Such a check is currently performed anyway by assume_aligned, and in my opinion should stay there. > + return std::assume_aligned<byte_alignment>(__p)[__i]; > + } > + > + constexpr typename offset_policy::data_handle_type > + offset(data_handle_type __p, size_t __i) const noexcept > + { > + if !consteval > + { > + _GLIBCXX_DEBUG_ASSERT( > + std::is_sufficiently_aligned<_ByteAlignment>(__p)); > + } > + return std::assume_aligned<byte_alignment>(__p) + __i; > + } > + }; > +#endif > + > _GLIBCXX_END_NAMESPACE_VERSION > } > #endif > diff --git a/libstdc++-v3/src/c++23/std.cc.in b/libstdc++-v3/src/c++23/ > std.cc.in > index 6f4214ed3a7..02e1713bdc3 100644 > --- a/libstdc++-v3/src/c++23/std.cc.in > +++ b/libstdc++-v3/src/c++23/std.cc.in > @@ -1851,7 +1851,8 @@ export namespace std > using std::layout_right; > using std::layout_stride; > using std::default_accessor; > - // FIXME layout_left_padded, layout_right_padded, aligned_accessor and > mdspan > + using std::aligned_accessor; > + // FIXME layout_left_padded, layout_right_padded and mdspan > } > #endif > > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned.cc > new file mode 100644 > index 00000000000..75581d3eb70 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned.cc > @@ -0,0 +1,43 @@ > +// { dg-do compile { target c++26 } } > +#include <mdspan> > + > +#include <testsuite_hooks.h> > + > +constexpr bool > +test_from_other() > +{ > + std::aligned_accessor<double, 4*sizeof(double)> a4; > + [[maybe_unused]] std::aligned_accessor<double, 2*sizeof(double)> a2(a4); > + static_assert(std::is_nothrow_convertible_v<std::aligned_accessor<char, > 4>, > + std::aligned_accessor<char, > 2>>); > + static_assert(!std::is_convertible_v<std::aligned_accessor<char, 2>, > + std::aligned_accessor<char, 4>>); > I would check !is_cosntructible, to indicate that there is no explicit constructor. > + return true; > +} > +static_assert(test_from_other()); > + > +constexpr bool > +test_from_default() > +{ > + std::default_accessor<double> ad; > + [[maybe_unused]] std::aligned_accessor<double, 4*sizeof(double)> a4(ad); > + static_assert(!std::is_convertible_v<std::default_accessor<char>, > + std::aligned_accessor<char, 1>>); > + static_assert(!std::is_convertible_v<std::default_accessor<char>, > + std::aligned_accessor<char, 2>>); > + static_assert(std::is_nothrow_constructible_v< > + std::aligned_accessor<char, 4>, std::default_accessor<char>>); > + return true; > +} > +static_assert(test_from_default()); > + > +constexpr bool > +test_to_default() > +{ > + std::aligned_accessor<double, 4*sizeof(double)> a4; > + [[maybe_unused]] std::default_accessor<double> ad = a4; > + static_assert(std::is_nothrow_convertible_v<std::aligned_accessor<char, > 2>, > + > std::default_accessor<char>>); > + return true; > +} > +static_assert(test_to_default()); > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_ftm.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_ftm.cc > new file mode 100644 > index 00000000000..361bf750c33 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_ftm.cc > @@ -0,0 +1,6 @@ > +// { dg-do compile { target c++26 } } > +#include <mdspan> > + > +#ifndef __cpp_lib_aligned_accessor > +#error "Missing FTM" > Updated how this test is performed. And I would rename this file to version. > +#endif > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_neg.cc > new file mode 100644 > index 00000000000..46d80bf4083 > --- /dev/null > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/aligned_neg.cc > @@ -0,0 +1,33 @@ > +// { dg-do compile { target c++26 } } > +#include<mdspan> > + > +#include <cstdint> > + > +std::aligned_accessor<uint32_t, 0> a; // { dg-error "required > from here" } > +std::aligned_accessor<uint32_t, 7> b; // { dg-error "required > from here" } > +std::aligned_accessor<uint32_t, size_t(-1)> c; // { dg-error "required > from here" } > + > +std::aligned_accessor<uint32_t, 2> d; // { dg-error "required > from here" } > + > +std::aligned_accessor<int[2], 32> e; // { dg-error "required > from here" } > + > +class Abstract > +{ > + virtual void > + foo() const = 0; > +}; > + > +class Derived : public Abstract > +{ > + void > + foo() const override > + { } > +}; > + > +std::aligned_accessor<Derived, alignof(int)> f_ok; > +std::aligned_accessor<Abstract, alignof(int)> f_err; // { dg-error > "required from here" } > + > +// { dg-prune-output "ByteAlignment must be a power of two" } > +// { dg-prune-output "ByteAlignment is too small for ElementType" } > +// { dg-prune-output "ElementType must not be an array type" } > +// { dg-prune-output "ElementType must not be an abstract" } > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_access_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_access_neg.cc > new file mode 100644 > index 00000000000..3511cef1c3a > --- /dev/null > +++ > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_access_neg.cc > Again, I feel like these negative checks belong to assume_alinged. > @@ -0,0 +1,23 @@ > +// { dg-do run { target c++26 xfail *-*-* } } > +// { dg-require-debug-mode "" } > + > +#include <mdspan> > +#include <array> > + > +void > +test_unaligned_access() > +{ > + constexpr size_t N = 4; > + alignas(N) std::array<char, 128> buffer{}; > + auto* unaligned = buffer.data() + 1; > + auto a = std::aligned_accessor<char, N>{}; > + > + [[maybe_unused]] char x = a.access(unaligned, 0); > +} > + > +int > +main() > +{ > + test_unaligned_access(); > + return 0; > +}; > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_offset_neg.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_offset_neg.cc > new file mode 100644 > index 00000000000..319da5ffef3 > --- /dev/null > +++ > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/debug/aligned_offset_neg.cc > @@ -0,0 +1,23 @@ > +// { dg-do run { target c++26 xfail *-*-* } } > +// { dg-require-debug-mode "" } > + > +#include <mdspan> > +#include <array> > + > +void > +test_unaligned_offset() > +{ > + constexpr size_t N = 4; > + alignas(N) std::array<char, 128> buffer{}; > + auto* unaligned = buffer.data() + 1; > + auto a = std::aligned_accessor<char, N>{}; > + > + [[maybe_unused]] char* x = a.offset(unaligned, 0); > +} > + > +int > +main() > +{ > + test_unaligned_offset(); > + return 0; > +}; > diff --git > a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/generic.cc > b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/generic.cc > index 600d152b690..f97946bd276 100644 > --- a/libstdc++-v3/testsuite/23_containers/mdspan/accessors/generic.cc > +++ b/libstdc++-v3/testsuite/23_containers/mdspan/accessors/generic.cc > @@ -98,10 +98,32 @@ > static_assert(test_class_properties<std::default_accessor<double>>()); > static_assert(test_accessor_policy<std::default_accessor<double>>()); > static_assert(test_ctor<DefaultAccessorTrait>()); > > +#ifdef __glibcxx_aligned_accessor > +struct AlignedAccessorTrait > +{ > + template<typename T> > + using type = std::aligned_accessor<T, alignof(T)>; > +}; > + > +static_assert(test_class_properties<std::aligned_accessor<double, > + > sizeof(double)>>()); > +static_assert(test_accessor_policy<std::aligned_accessor<double, > + > sizeof(double)>>()); > +static_assert(test_accessor_policy<std::aligned_accessor<double, > + > 2*sizeof(double)>>()); > +static_assert(test_ctor<AlignedAccessorTrait>()); > +#endif > + > template<typename A> > constexpr size_t > accessor_alignment = sizeof(typename A::element_type); > > +#ifdef __glibcxx_aligned_accessor > +template<typename T, size_t N> > + constexpr size_t > + accessor_alignment<std::aligned_accessor<T, N>> = N; > +#endif > + > template<typename Accessor> > constexpr void > test_access(Accessor accessor) > @@ -137,5 +159,10 @@ main() > { > test_all<std::default_accessor<double>>(); > static_assert(test_all<std::default_accessor<double>>()); > + > +#ifdef __glibcxx_aligned_accessor > + test_all<std::aligned_accessor<double, 4*sizeof(double)>>(); > + static_assert(test_all<std::aligned_accessor<double, > 4*sizeof(double)>>()); > +#endif > return 0; > } > -- > 2.49.0 > >