Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
ah_mdspan_test.cc
Go to the documentation of this file.
1/*
2 Aleph_w
3
4 Data structures & Algorithms
5 https://github.com/lrleon/Aleph-w
6
7 This file is part of Aleph-w library
8
9 Copyright (c) 2002-2026 Leandro Rabindranath Leon
10
11 Permission is hereby granted, free of charge, to any person obtaining a copy
12 of this software and associated documentation files (the "Software"), to deal
13 in the Software without restriction, including without limitation the rights
14 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
15 copies of the Software, and to permit persons to whom the Software is
16 furnished to do so, subject to the following conditions:
17
18 The above copyright notice and this permission notice shall be included in all
19 copies or substantial portions of the Software.
20
21 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
22 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
23 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
24 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
25 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
26 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
27 SOFTWARE.
28*/
29
38#include <array>
39#include <stdexcept>
40#include <type_traits>
41#include <vector>
42
43#include <gtest/gtest.h>
44
45#include <ah-mdspan.H>
46
47using Aleph::dextents;
49using Aleph::extents;
52using Aleph::mdspan;
53
54// Compile-time regression: operator()/at() must reject a wrong number of
55// indices for the view's rank instead of silently reading past a local
56// array (a real out-of-bounds stack read was reproduced before this
57// constraint existed — see the PR review discussion). A rank-2 view must
58// accept exactly 2 indices, not 1 and not 3.
60
61// A bare top-level `requires(Rank2 m) { m(1); }` would NOT reliably detect
62// this: once `Rank2` is a fully concrete (non-dependent) type, an unsatisfied
63// call is a plain "no matching function" error, not a substitution failure
64// in the immediate context of a template parameter -- so it is not silently
65// absorbed. Wrapping the check in a genuine template (a concept) restores
66// the SFINAE-friendly behavior actually needed here.
67template <class M, class... Args>
68concept CanCall = requires(M m, Args... args) { m(args...); };
69template <class M, class... Args>
70concept CanCallAt = requires(M m, Args... args) { m.at(args...); };
71
72static_assert(CanCall<Rank2, int, int>);
73static_assert(not CanCall<Rank2, int>);
74static_assert(not CanCall<Rank2, int, int, int>);
75static_assert(CanCallAt<Rank2, int, int>);
76static_assert(not CanCallAt<Rank2, int>);
77static_assert(not CanCallAt<Rank2, int, int, int>);
78
79TEST(AhMdspan, DetectionMacroIsDefined)
80{
81 // Must always be defined to 0 or 1, regardless of -std.
82 constexpr int v = ALEPH_HAS_STD_MDSPAN;
83 EXPECT_TRUE(v == 0 or v == 1);
84}
85
86TEST(AhMdspan, DextentsRankAndRankDynamic)
87{
88 using Ext = dextents<size_t, 3>;
89 static_assert(Ext::rank() == 3);
90 static_assert(Ext::rank_dynamic() == 3);
91 Ext e(2, 3, 4);
92 EXPECT_EQ(e.extent(0), 2u);
93 EXPECT_EQ(e.extent(1), 3u);
94 EXPECT_EQ(e.extent(2), 4u);
95}
96
97TEST(AhMdspan, MixedStaticAndDynamicExtents)
98{
99 // Axis 0 dynamic, axis 1 fixed to 4.
101 static_assert(Ext::rank() == 2);
102 static_assert(Ext::rank_dynamic() == 1);
103 static_assert(Ext::static_extent(1) == 4);
104 static_assert(Ext::static_extent(0) == dynamic_extent);
105
106 Ext e(7); // only the one dynamic axis needs a value
107 EXPECT_EQ(e.extent(0), 7u);
108 EXPECT_EQ(e.extent(1), 4u);
109}
110
111TEST(AhMdspan, ExtentsFromArray)
112{
113 std::array<size_t, 2> shape{3, 5};
114 dextents<size_t, 2> e(shape);
115 EXPECT_EQ(e.extent(0), 3u);
116 EXPECT_EQ(e.extent(1), 5u);
117}
118
119TEST(AhMdspan, ExtentsEquality)
120{
121 dextents<size_t, 2> a(2, 3);
122 dextents<size_t, 2> b(2, 3);
123 dextents<size_t, 2> c(3, 2);
124 EXPECT_TRUE(a == b);
125 EXPECT_FALSE(a == c);
126}
127
128TEST(AhMdspan, BasicConstructionAndShape)
129{
130 std::vector<int> buf(2 * 3, 0);
131 mdspan<int, dextents<size_t, 2>> m(buf.data(), 2, 3);
132
133 EXPECT_EQ(m.rank(), 2u);
134 EXPECT_EQ(m.extent(0), 2u);
135 EXPECT_EQ(m.extent(1), 3u);
136 EXPECT_EQ(m.size(), 6u);
137 EXPECT_FALSE(m.empty());
138 EXPECT_EQ(m.data_handle(), buf.data());
139}
140
141TEST(AhMdspan, OperatorRequiresExactRankArguments)
142{
143 std::vector<int> buf(6, 0);
144 mdspan<int, dextents<size_t, 2>> m(buf.data(), 2, 3);
145
146 // Valid call: 2 indices for rank-2 span
147 static_assert(std::is_invocable_v<decltype(m), int, int>);
148
149 // Invalid calls: wrong arity
150 static_assert(not std::is_invocable_v<decltype(m), int>);
151 static_assert(not std::is_invocable_v<decltype(m), int, int, int>);
152}
153
154TEST(AhMdspan, ConstructFromArrayOfDynamicExtents)
155{
156 std::vector<int> buf(6, 0);
157 std::array<size_t, 2> shape{2, 3};
158 mdspan<int, dextents<size_t, 2>> m(buf.data(), shape);
159 EXPECT_EQ(m.extent(0), 2u);
160 EXPECT_EQ(m.extent(1), 3u);
161}
162
163TEST(AhMdspan, RowMajorIsDefaultAndMatchesFlatBufferOrder)
164{
165 // layout_right (row-major): the LAST axis varies fastest, i.e. m(i, j)
166 // maps to buf[i * ncols + j] -- exactly Aleph's own Array/C convention.
167 std::vector<int> buf(2 * 3, 0);
168 mdspan<int, dextents<size_t, 2>> m(buf.data(), 2, 3);
169
170 int next = 0;
171 for (size_t i = 0; i < m.extent(0); ++i)
172 for (size_t j = 0; j < m.extent(1); ++j)
173 m(i, j) = next++;
174
175 EXPECT_EQ(buf, (std::vector<int>{0, 1, 2, 3, 4, 5}));
176}
177
178TEST(AhMdspan, LayoutLeftIsColumnMajor)
179{
180 // layout_left (column-major): the FIRST axis varies fastest.
181 std::vector<int> buf(2 * 3, 0);
183
184 int next = 0;
185 for (size_t j = 0; j < m.extent(1); ++j)
186 for (size_t i = 0; i < m.extent(0); ++i)
187 m(i, j) = next++;
188
189 EXPECT_EQ(buf, (std::vector<int>{0, 1, 2, 3, 4, 5}));
190}
191
192TEST(AhMdspan, WriteThroughViewIsVisibleThroughBuffer)
193{
194 std::vector<double> buf(4, 0.0);
195 mdspan<double, dextents<size_t, 2>> m(buf.data(), 2, 2);
196 m(0, 0) = 1.0;
197 m(0, 1) = 2.0;
198 m(1, 0) = 3.0;
199 m(1, 1) = 4.0;
200 EXPECT_EQ(buf, (std::vector<double>{1.0, 2.0, 3.0, 4.0}));
201}
202
203TEST(AhMdspan, ReadOnlyViewOverConstElement)
204{
205 const std::vector<int> buf{9, 8, 7, 6};
207 EXPECT_EQ(m(0), 9);
208 EXPECT_EQ(m(2), 7);
209 static_assert(std::is_const_v<std::remove_reference_t<decltype(m(0))>>);
210}
211
212TEST(AhMdspan, AtThrowsOnOutOfRangeIndex)
213{
214 std::vector<int> buf(6, 0);
215 mdspan<int, dextents<size_t, 2>> m(buf.data(), 2, 3);
216 EXPECT_NO_THROW((void) m.at(1, 2));
217 EXPECT_THROW((void) m.at(2, 0), std::out_of_range);
218 EXPECT_THROW((void) m.at(0, 3), std::out_of_range);
219}
220
221TEST(AhMdspan, OneDimensionalView)
222{
223 std::vector<int> buf{10, 20, 30};
224 mdspan<int, dextents<size_t, 1>> m(buf.data(), 3);
225 EXPECT_EQ(m.rank(), 1u);
226 EXPECT_EQ(m(0), 10);
227 EXPECT_EQ(m(2), 30);
228}
229
230TEST(AhMdspan, ThreeDimensionalRowMajorRoundTrip)
231{
232 std::vector<int> buf(2 * 2 * 2, -1);
233 mdspan<int, dextents<size_t, 3>> m(buf.data(), 2, 2, 2);
234
235 int next = 0;
236 for (size_t i = 0; i < 2; ++i)
237 for (size_t j = 0; j < 2; ++j)
238 for (size_t k = 0; k < 2; ++k)
239 m(i, j, k) = next++;
240
241 for (int v : buf)
242 EXPECT_NE(v, -1);
243 EXPECT_EQ(m(1, 1, 1), 7);
244 EXPECT_EQ(buf.back(), 7);
245}
246
247TEST(AhMdspan, DefaultConstructedIsEmptyWithNullData)
248{
250 EXPECT_EQ(m.data_handle(), nullptr);
251 EXPECT_TRUE(m.empty());
252 EXPECT_EQ(m.size(), 0u);
253}
254
255TEST(AhMdspan, MultipleIndependentViewsOverSameBufferDoNotInterfere)
256{
257 std::vector<int> buf(4, 0);
258 mdspan<int, dextents<size_t, 2>> m1(buf.data(), 2, 2);
259 mdspan<int, dextents<size_t, 2>> m2(buf.data(), 2, 2);
260 m1(0, 0) = 42;
261 EXPECT_EQ(m2(0, 0), 42); // same underlying buffer: aliasing is intentional
262}
263
264TEST(AhMdspan, MutableViewConvertsImplicitlyToConstView)
265{
266 // Mirrors T* -> const T* implicit conversion.
267 int buf[4] = {1, 2, 3, 4};
269
270 auto reader = [](mdspan<const int, dextents<size_t, 2>> v) { return v(1, 1); };
271 EXPECT_EQ(reader(m), 4);
272}
#define ALEPH_HAS_STD_MDSPAN
Non-owning multidimensional array view (Aleph::mdspan).
Shape (per-axis sizes) of an mdspan, mixing compile-time and run-time extents.
Definition ah-mdspan.H:303
Non-owning view of a contiguous buffer as a multidimensional grid.
Definition ah-mdspan.H:576
constexpr size_t size() const noexcept
Returns the number of entries in the table.
Definition hashDry.H:619
void empty()
Clears all entries and resets to default capacity.
Definition hashDry.H:601
#define TEST(name)
constexpr size_t dynamic_extent
Sentinel marking an axis whose extent is only known at run time.
Definition ah-mdspan.H:277
typename decltype(mdspan_detail::dextents_of< IndexType >(std::make_index_sequence< Rank >{}))::type dextents
extents<IndexType, ...> with all Rank axes dynamic — the common case.
Definition ah-mdspan.H:404
Column-major layout: the first axis varies fastest (Fortran convention).
Definition ah-mdspan.H:467
Row-major layout: the last axis varies fastest (C convention).
Definition ah-mdspan.H:413
FooMap m(5, fst_unit_pair_hash, snd_unit_pair_hash)
static int * k