On 7/8/25 17:37, Tomasz Kaminski wrote:
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.
Correct, I'll update the patch accordingly.
+ 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.
I fully agree that offset and access can rely on assume_aligned
to do the check.
However, testing that the two methods fail for non-aligned input seems
prudent. We're testing a property of offset and access. They happen to
rely on assume_aligned to perform the check, but that's a detail. We're
not testing how it does the check, only that the check happens.
Since it might make the tests too brittle, feel free to insist.
@@ -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