Files
gcc/libstdc++-v3/testsuite/23_containers/mdspan/mdspan.cc
Luc Grosheintz 29d53f6213 libstdc++: Fix forwarding of custom IndexType in mdspan [PR121061]
The second bug report in PR121061 is that the conversion of custom
OtherIndexType to IndexType is incorrectly not done via r-value
references.

This commit fixes the forwarding issue, adds a custom IndexType called
RValueInt, which only allows conversion to int via r-value reference.

	PR libstdc++/121061

libstdc++-v3/ChangeLog:

	* include/std/mdspan (extents::extents): Perform conversion to
	index_type of an r-value reference.
	(layout_left::mapping::operator()): Ditto.
	(layout_right::mapping::operator()): Ditto.
	(layout_stride::mapping::operator()): Ditto.
	* testsuite/23_containers/mdspan/extents/custom_integer.cc: Add
	tests for RValueInt and MutatingInt.
	* testsuite/23_containers/mdspan/int_like.h (RValueInt): Add.
	* testsuite/23_containers/mdspan/layouts/mapping.cc: Test with
	RValueInt.
	* testsuite/23_containers/mdspan/mdspan.cc: Ditto.

Reviewed-by: Jonathan Wakely <jwakely@redhat.com>
Reviewed-by: Tomasz Kamiński <tkaminsk@redhat.com>
Signed-off-by: Luc Grosheintz <luc.grosheintz@gmail.com>
2025-07-17 16:12:52 +02:00

718 lines
19 KiB
C++

// { dg-do run { target c++23 } }
#include <mdspan>
#include <testsuite_hooks.h>
#include "int_like.h"
#include "layout_like.h"
constexpr auto dyn = std::dynamic_extent;
template<typename MDSpan, typename T, typename E, typename L = std::layout_right,
typename A = std::default_accessor<T>>
constexpr void
assert_typedefs()
{
static_assert(std::same_as<typename MDSpan::extents_type, E>);
static_assert(std::same_as<typename MDSpan::layout_type, L>);
static_assert(std::same_as<typename MDSpan::accessor_type, A>);
static_assert(std::same_as<typename MDSpan::mapping_type,
typename L::mapping<E>>);
static_assert(std::same_as<typename MDSpan::element_type, T>);
static_assert(std::same_as<typename MDSpan::value_type,
std::remove_const_t<T>>);
static_assert(std::same_as<typename MDSpan::index_type,
typename E::index_type>);
static_assert(std::same_as<typename MDSpan::size_type,
typename E::size_type>);
static_assert(std::same_as<typename MDSpan::rank_type,
typename E::rank_type>);
static_assert(std::same_as<typename MDSpan::data_handle_type,
typename A::data_handle_type>);
static_assert(std::same_as<typename MDSpan::reference,
typename A::reference>);
}
template<typename T, typename E, typename L, template<typename U> typename A>
constexpr void
test_typedefs()
{ assert_typedefs<std::mdspan<T, E, L, A<T>>, T, E, L, A<T>>(); }
constexpr void
test_typedefs_all()
{
using E = std::extents<int, 1, 2>;
using L = std::layout_left;
test_typedefs<double, E, L, std::default_accessor>();
test_typedefs<const double, E, L, std::default_accessor>();
}
template<typename MDSpan>
constexpr void
test_rank()
{
using Extents = typename MDSpan::extents_type;
static_assert(MDSpan::rank() == Extents::rank());
static_assert(MDSpan::rank_dynamic() == Extents::rank_dynamic());
}
constexpr bool
test_rank_all()
{
test_rank<std::mdspan<double, std::extents<int>>>();
test_rank<std::mdspan<double, std::extents<int, 1>>>();
test_rank<std::mdspan<double, std::extents<int, dyn>>>();
return true;
}
template<typename Extents>
constexpr void
test_extent(Extents exts)
{
double data = 1.0;
auto md = std::mdspan(&data, exts);
using MDSpan = decltype(md);
for(size_t i = 0; i < MDSpan::rank(); ++i)
{
VERIFY(MDSpan::static_extent(i) == Extents::static_extent(i));
VERIFY(md.extent(i) == exts.extent(i));
}
}
constexpr bool
test_extent_all()
{
// For rank == 0, check existence of the methods without calling them.
test_extent(std::extents<int>{});
test_extent(std::extents<int, 0>{});
test_extent(std::extents<int, dyn>{});
return true;
}
template<typename MDSpan>
constexpr void
test_class_properties()
{
static_assert(std::copyable<MDSpan>);
static_assert(std::is_nothrow_move_constructible_v<MDSpan>);
static_assert(std::is_nothrow_move_assignable_v<MDSpan>);
static_assert(std::is_nothrow_swappable_v<MDSpan>);
constexpr bool trivially_copyable =
std::is_trivially_copyable_v<typename MDSpan::accessor_type>
&& std::is_trivially_copyable_v<typename MDSpan::mapping_type>
&& std::is_trivially_copyable_v<typename MDSpan::data_handle_type>;
static_assert(std::is_trivially_copyable_v<MDSpan> == trivially_copyable);
}
constexpr bool
test_class_properties_all()
{
test_class_properties<std::mdspan<double, std::extents<int>>>();
test_class_properties<std::mdspan<double, std::extents<int, 1>>>();
test_class_properties<std::mdspan<double, std::extents<int, dyn>>>();
return true;
}
constexpr bool
test_default_ctor()
{
static_assert(!std::is_default_constructible_v<std::mdspan<double,
std::extents<int>>>);
static_assert(!std::is_default_constructible_v<std::mdspan<double,
std::extents<int, 1>>>);
static_assert(std::is_default_constructible_v<std::mdspan<double,
std::extents<int, dyn>>>);
std::mdspan<double, std::extents<int, dyn>> md;
VERIFY(md.data_handle() == nullptr);
VERIFY(md.empty());
return true;
}
constexpr bool
test_from_other()
{
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto mapping = std::layout_right::mapping(exts);
constexpr size_t n = mapping.required_span_size();
std::array<double, n> storage{};
auto md1 = std::mdspan(storage.data(), exts);
auto md2 = std::mdspan<double, std::dextents<int, 3>>(md1);
VERIFY(md1.data_handle() == md2.data_handle());
VERIFY(md1.size() == md2.size());
static_assert(!std::is_convertible_v<
std::mdspan<double, std::extents<unsigned int, 2>>,
std::mdspan<double, std::extents<int, 2>>>);
static_assert(std::is_convertible_v<
std::mdspan<double, std::extents<int, 2>>,
std::mdspan<const double, std::extents<int, 2>>>);
static_assert(!std::is_constructible_v<
std::mdspan<double, std::extents<int, 2>>,
std::mdspan<const double, std::extents<int, 2>>>);
return true;
}
template<typename T, typename E, typename L = std::layout_right,
typename A = std::default_accessor<T>>
constexpr void
assert_deduced_typedefs(auto md)
{ assert_typedefs<decltype(md), T, E, L, A>(); }
constexpr bool
test_from_carray()
{
constexpr size_t n = 5;
double data[n] = {1.1, 2.2, 3.3, 4.4, 5.5};
auto md = std::mdspan(data);
assert_deduced_typedefs<double, std::extents<size_t, n>>(md);
VERIFY(md.rank() == 1);
VERIFY(md.rank_dynamic() == 0);
VERIFY(md[2] == data[2]);
return true;
}
constexpr bool
test_from_pointer()
{
double value = 12.3;
auto md = std::mdspan(&value);
assert_deduced_typedefs<double, std::extents<size_t>>(md);
VERIFY(md.rank() == 0);
VERIFY(md.rank_dynamic() == 0);
VERIFY(md[] == value);
return true;
}
constexpr bool
test_from_pointer_and_shape()
{
constexpr size_t n = 6;
std::array<double, n> data{1.1, 2.2, 3.3, 4.4, 5.5, 6.6};
std::array<int, 2> shape{2, 3};
std::span<const int, 2> shape_view(shape);
auto verify = [&data](auto md)
{
assert_deduced_typedefs<double, std::dextents<size_t, 2>>(md);
VERIFY(md.rank() == 2);
VERIFY(md.rank_dynamic() == 2);
VERIFY((md[0, 0]) == data[0]);
VERIFY((md[0, 1]) == data[1]);
VERIFY((md[1, 0]) == data[3]);
};
verify(std::mdspan(data.data(), shape[0], shape[1]));
verify(std::mdspan(data.data(), shape));
verify(std::mdspan(data.data(), shape_view));
std::mdspan<double, std::dextents<size_t, 2>> md1 = {data.data(), shape};
verify(md1);
std::mdspan<double, std::dextents<size_t, 2>> md2 = {data.data(), shape_view};
verify(md2);
static_assert(std::is_constructible_v<
std::mdspan<float, std::extents<int, 3, 5>>, float*>);
static_assert(!std::is_constructible_v<
std::mdspan<float, std::extents<int, 3, 5>>, float*, int>);
static_assert(std::is_constructible_v<
std::mdspan<float, std::extents<int, 3, 5>>, float*, int, int>);
static_assert(std::is_constructible_v<
std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int, 0>>);
static_assert(std::is_constructible_v<
std::mdspan<float, std::extents<int, 3, 5>>, float*, std::span<int, 2>>);
static_assert(!std::is_convertible_v<
float*, std::mdspan<float, std::extents<int, 3, 5>>>);
static_assert(std::is_constructible_v<
std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, 2>>);
static_assert(!std::is_constructible_v<
std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, 1>>);
static_assert(!std::is_constructible_v<
std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, 3>>);
static_assert(!std::is_constructible_v<
std::mdspan<float, std::dextents<int, 2>>, float*, std::span<int, dyn>>);
return true;
}
constexpr bool
test_from_pointer_and_integral_constant()
{
std::array<double, 6> buffer{};
double * ptr = buffer.data();
auto verify = [ptr](auto actual, auto exts)
{
auto expected = std::mdspan<double, decltype(exts)>(ptr, exts);
static_assert(std::same_as<decltype(actual), decltype(expected)>);
VERIFY(actual.extents() == expected.extents());
};
auto c3 = std::integral_constant<int, 3>{};
auto c6 = std::integral_constant<int, 6>{};
verify(std::mdspan(ptr, 6), std::extents(6));
verify(std::mdspan(ptr, c6), std::extents(c6));
verify(std::mdspan(ptr, 2, c3), std::extents(2, c3));
return true;
}
constexpr bool
test_from_extents()
{
constexpr size_t n = 3*5*7;
std::array<double, n> storage{};
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto md = std::mdspan(storage.data(), exts);
assert_deduced_typedefs<double, Extents>(md);
VERIFY(md.data_handle() == storage.data());
VERIFY(md.extents() == exts);
return true;
}
constexpr bool
test_from_mapping()
{
constexpr size_t n = 3*5*7;
std::array<double, n> storage{};
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto m = std::layout_left::mapping(exts);
auto md = std::mdspan(storage.data(), m);
assert_deduced_typedefs<double, Extents, std::layout_left>(md);
VERIFY(md.data_handle() == storage.data());
VERIFY(md.mapping() == m);
return true;
}
constexpr bool
test_from_accessor()
{
constexpr size_t n = 3*5*7;
std::array<double, n> storage{};
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto m = std::layout_left::mapping(exts);
auto a = std::default_accessor<double>{};
auto md = std::mdspan(storage.data(), m, a);
assert_deduced_typedefs<double, Extents, std::layout_left>(md);
VERIFY(md.data_handle() == storage.data());
VERIFY(md.mapping() == m);
return true;
}
template<typename MDSpan, typename Pointer, typename... Ints>
concept has_pack_ctor = requires
{
{ MDSpan(Pointer{}, Ints(0)...) } -> std::same_as<MDSpan>;
};
template<typename CustomInt, bool ValidForPacks, bool ValidForArrays>
constexpr bool
test_from_int_like()
{
constexpr size_t n = 3*5*7;
std::array<double, n> storage{};
auto verify = [&](auto md)
{
VERIFY(md.data_handle() == storage.data());
VERIFY(md.extent(0) == 3);
VERIFY(md.extent(1) == 5);
VERIFY(md.extent(2) == 7);
};
static_assert(has_pack_ctor<std::mdspan<float, std::dextents<int, 3>>,
float*, CustomInt, int, CustomInt> == ValidForPacks);
static_assert(std::is_constructible_v<
std::mdspan<float, std::dextents<int, 3>>, float*,
std::span<CustomInt, 3>> == ValidForArrays);
static_assert(std::is_constructible_v<
std::mdspan<float, std::dextents<int, 3>>, float*,
std::array<CustomInt, 3>> == ValidForArrays);
if constexpr (ValidForPacks)
verify(std::mdspan(storage.data(), CustomInt(3), 5, CustomInt(7)));
if constexpr (ValidForArrays)
{
auto shape = std::array{CustomInt(3), CustomInt(5), CustomInt(7)};
auto shape_view = std::span<CustomInt, 3>{shape};
verify(std::mdspan(storage.data(), shape));
verify(std::mdspan(storage.data(), shape_view));
}
return true;
}
template<typename T, bool NothrowConstructible = true,
bool NothrowAssignable = true>
class OpaqueAccessor
{
struct Handle
{
constexpr
Handle(T * other)
: ptr(other)
{ }
constexpr
Handle(const Handle&) noexcept(NothrowConstructible) = default;
constexpr
Handle(Handle&&) noexcept(NothrowConstructible) = default;
constexpr Handle&
operator=(const Handle&) noexcept(NothrowAssignable) = default;
constexpr Handle&
operator=(Handle&&) noexcept(NothrowAssignable) = default;
T * ptr;
};
public:
using element_type = T;
using reference = T&;
using data_handle_type = Handle;
using offset_policy = OpaqueAccessor;
reference
access(data_handle_type p, size_t i) const
{
++access_count;
return p.ptr[i];
}
typename offset_policy::data_handle_type
offset(data_handle_type p, size_t i) const
{
++offset_count;
return typename offset_policy::data_handle_type{(void*)(p.ptr + i)};
}
mutable size_t access_count = 0;
mutable size_t offset_count = 0;
};
void
test_from_opaque_accessor()
{
constexpr size_t n = 3*5*7;
std::array<double, n> storage{};
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto m = std::layout_left::mapping(exts);
auto a = OpaqueAccessor<double>{};
auto handle = OpaqueAccessor<double>::data_handle_type{storage.data()};
auto md = std::mdspan(handle, m, a);
using MDSpan = decltype(md);
static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>);
VERIFY((md[0, 0, 0]) == 0.0);
VERIFY(md.accessor().access_count == 1);
VERIFY((md[2, 4, 6]) == 0.0);
VERIFY(md.accessor().access_count == 2);
}
template<typename T, typename Base>
class BaseClassAccessor
{
public:
using element_type = T;
using reference = Base&;
using data_handle_type = T*;
using offset_policy = BaseClassAccessor;
static_assert(std::common_reference_with<reference&&, element_type&>);
reference
access(data_handle_type p, size_t i) const
{ return p[i]; }
typename offset_policy::data_handle_type
offset(data_handle_type p, size_t i) const
{ return typename offset_policy::data_handle_type{p + i}; }
};
struct Base
{
double value = 1.0;
};
struct Derived : Base
{
double value = 2.0;
};
void
test_from_base_class_accessor()
{
constexpr size_t n = 3*5*7;
std::array<Derived, n> storage{};
using Extents = std::extents<int, 3, 5, 7>;
auto exts = Extents{};
auto m = std::layout_left::mapping(exts);
auto a = BaseClassAccessor<Derived, Base>{};
auto md = std::mdspan(storage.data(), m, a);
using MDSpan = decltype(md);
static_assert(std::same_as<MDSpan::accessor_type, decltype(a)>);
static_assert(std::same_as<decltype(md[0, 0, 0]), Base&>);
VERIFY((md[0, 0, 0].value) == 1.0);
VERIFY((md[2, 4, 6].value) == 1.0);
}
constexpr bool
test_from_mapping_like()
{
double data = 1.1;
auto m = LayoutLike::mapping<std::extents<int, 1, 2, 3>>{};
auto md = std::mdspan(&data, m);
VERIFY((md[0, 0, 0]) == data);
VERIFY((md[0, 1, 2]) == data);
return true;
}
template<typename MDSpan>
constexpr void
test_empty(MDSpan md)
{
VERIFY(md.empty() == (md.size() == 0));
}
constexpr bool
test_empty_all()
{
test_empty(std::mdspan<double, std::extents<int, dyn>>{});
return true;
}
template<typename MDSpan, typename... Args>
concept indexable = requires (MDSpan md, Args... args)
{
{ md[args...] } -> std::same_as<typename MDSpan::reference>;
};
template<typename Int, bool ValidForPacks, bool ValidForArrays>
constexpr bool
test_access()
{
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[Int(i), Int(j), Int(k)]) == 1.0);
if constexpr (ValidForArrays)
{
std::array<Int, 3> ijk{Int(i), Int(j), Int(k)};
VERIFY((md[ijk]) == 1.0);
VERIFY((md[std::span(ijk)]) == 1.0);
}
storage[mapping(i, j, k)] = 0.0;
}
if constexpr (!ValidForPacks)
static_assert(!indexable<MDSpan, Int, int, Int>);
if constexpr (!ValidForArrays)
{
static_assert(!indexable<MDSpan, std::array<Int, 3>>);
static_assert(!indexable<MDSpan, std::span<Int, 3>>);
}
return true;
}
constexpr bool
test_swap()
{
using Extents = std::dextents<int, 2>;
auto e1 = Extents{3, 5};
auto e2 = Extents{7, 11};
std::array<double, 3*5> s1{};
std::array<double, 7*11> s2{};
auto md1 = std::mdspan(s1.data(), e1);
auto md2 = std::mdspan(s2.data(), e2);
std::swap(md1, md2);
VERIFY(md1.data_handle() == s2.data());
VERIFY(md2.data_handle() == s1.data());
VERIFY(md1.size() == s2.size());
VERIFY(md2.size() == s1.size());
return true;
}
namespace adl
{
template<typename T>
struct SwappableAccessor
{
using element_type = T;
using reference = T&;
using data_handle_type = T*;
using offset_policy = SwappableAccessor;
reference
access(data_handle_type p, size_t i) const
{ return p[i]; }
typename offset_policy::data_handle_type
offset(data_handle_type p, size_t i) const
{ return p + i; }
friend void
swap(SwappableAccessor&, SwappableAccessor&)
{ ++swap_count; }
static inline size_t swap_count = 0;
};
}
void
test_swap_adl()
{
using Extents = std::extents<int, dyn>;
using Layout = std::layout_left;
using Accessor = adl::SwappableAccessor<double>;
Accessor::swap_count = 0;
std::mdspan<double, Extents, Layout, Accessor> m1, m2;
swap(m1, m2);
VERIFY(Accessor::swap_count == 1);
}
template<bool Constructible, bool Assignable>
constexpr void
test_nothrow_movable()
{
using Layout = std::layout_left;
using Extents = std::dextents<int, 3>;
using Accessor = OpaqueAccessor<int, Constructible, Assignable>;
using Handle = Accessor::data_handle_type;
static_assert(std::is_nothrow_move_assignable_v<Accessor>);
static_assert(std::is_nothrow_move_constructible_v<Accessor>);
static_assert(std::is_nothrow_move_assignable_v<Handle> == Assignable);
static_assert(std::is_nothrow_move_constructible_v<Handle> == Constructible);
using MDSpan = std::mdspan<int, Extents, Layout, Accessor>;
static_assert(std::is_nothrow_move_assignable_v<MDSpan> == Assignable);
static_assert(std::is_nothrow_move_constructible_v<MDSpan> == Constructible);
}
constexpr void
test_nothrow_movable_all()
{
using MDSpan = std::mdspan<double, std::dextents<int, 3>>;
static_assert(std::is_nothrow_move_assignable_v<MDSpan>);
static_assert(std::is_nothrow_move_constructible_v<MDSpan>);
test_nothrow_movable<true, true>();
test_nothrow_movable<true, false>();
test_nothrow_movable<false, true>();
test_nothrow_movable<false, false>();
}
int
main()
{
test_typedefs_all();
test_rank_all();
test_extent_all();
static_assert(test_extent_all());
test_class_properties_all();
static_assert(test_class_properties_all());
test_empty_all();
static_assert(test_empty_all());
test_default_ctor();
static_assert(test_default_ctor());
test_from_other();
static_assert(test_from_other());
test_from_carray();
static_assert(test_from_carray());
test_from_pointer_and_shape();
static_assert(test_from_pointer_and_shape());
test_from_pointer_and_integral_constant();
static_assert(test_from_pointer_and_integral_constant());
test_from_extents();
static_assert(test_from_extents());
test_from_mapping();
static_assert(test_from_mapping());
test_from_accessor();
static_assert(test_from_accessor());
test_from_int_like<int, true, true>();
static_assert(test_from_int_like<int, true, true>());
test_from_int_like<IntLike, true, true>();
test_from_int_like<ThrowingInt, false, false>();
test_from_int_like<MutatingInt, true, false>();
test_from_int_like<RValueInt, true, false>();
test_from_opaque_accessor();
test_from_base_class_accessor();
test_from_mapping_like();
static_assert(test_from_mapping_like());
test_access<int, true, true>();
static_assert(test_access<int, true, true>());
test_access<IntLike, true, true>();
test_access<ThrowingInt, false, false>();
test_access<MutatingInt, true, false>();
test_access<RValueInt, true, false>();
test_swap();
static_assert(test_swap());
test_swap_adl();
test_nothrow_movable_all();
return 0;
}