Aleph-w 3.0
A C++ Library for Data Structures and Algorithms
Loading...
Searching...
No Matches
small_vector_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
40#include <memory>
41#include <stdexcept>
42#include <string>
43#include <type_traits>
44#include <vector>
45
46#include <gtest/gtest.h>
47
48#include <tpl_small_vector.H>
49
51
52namespace
53{
54// Instrumented element type: counts live instances so tests can prove that
55// placement-constructed elements are destroyed exactly once.
56struct Probe
57{
58 static int live;
59 int value = 0;
60
61 Probe() { ++live; }
62 explicit Probe(int v) : value(v) { ++live; }
63 Probe(const Probe &p) : value(p.value) { ++live; }
64 Probe(Probe &&p) noexcept : value(p.value) { ++live; }
65 Probe &operator=(const Probe &) = default;
66 Probe &operator=(Probe &&) noexcept = default;
67 ~Probe() { --live; }
68};
69
70int Probe::live = 0;
71
72struct Throwing_Copy
73{
74 static int live;
75 static int copies;
76 static int throw_after;
77 int value = 0;
78
79 explicit Throwing_Copy(int v) : value(v) { ++live; }
80
81 Throwing_Copy(const Throwing_Copy &x) : value(x.value)
82 {
83 if (throw_after >= 0 and copies++ >= throw_after)
84 throw std::runtime_error("copy failed");
85 ++live;
86 }
87
88 Throwing_Copy(Throwing_Copy &&x) noexcept : value(x.value) { ++live; }
89
90 Throwing_Copy &operator=(const Throwing_Copy &) = default;
91 Throwing_Copy &operator=(Throwing_Copy &&) noexcept = default;
92
93 ~Throwing_Copy() { --live; }
94
95 static void reset(int limit = -1)
96 {
97 copies = 0;
98 throw_after = limit;
99 }
100};
101
102int Throwing_Copy::live = 0;
103int Throwing_Copy::copies = 0;
104int Throwing_Copy::throw_after = -1;
105
106struct Throwing_Move
107{
108 int value = 0;
109
110 Throwing_Move() = default;
111 explicit Throwing_Move(int v) : value(v) {}
112 Throwing_Move(const Throwing_Move &) = delete;
113 Throwing_Move(Throwing_Move &&x) noexcept(false) : value(x.value) {}
114 Throwing_Move &operator=(const Throwing_Move &) = delete;
115 Throwing_Move &operator=(Throwing_Move &&) noexcept(false) = default;
116};
117
118static_assert(noexcept(std::declval<SmallVector<int, 2> &>().swap
119 (std::declval<SmallVector<int, 2> &>())));
120static_assert(not noexcept(std::declval<SmallVector<Throwing_Move, 2> &>().swap
121 (std::declval<SmallVector<Throwing_Move, 2> &>())));
122} // namespace
123
124TEST(SmallVector, StaysInlineUpToNThenSpills)
125{
127 EXPECT_TRUE(v.is_small());
128 EXPECT_EQ(v.capacity(), 4u);
129 EXPECT_TRUE(v.is_empty());
130
131 for (int i = 0; i < 4; ++i)
132 v.append(i);
133 EXPECT_TRUE(v.is_small()); // exactly N elements: still inline
134 EXPECT_EQ(v.size(), 4u);
135
136 v.append(4); // spill
137 EXPECT_FALSE(v.is_small());
138 EXPECT_GE(v.capacity(), 5u);
139 ASSERT_EQ(v.size(), 5u);
140 for (int i = 0; i < 5; ++i)
141 EXPECT_EQ(v[i], i); // values preserved across the spill
142}
143
144TEST(SmallVector, InlineBufferIsInsideTheObject)
145{
147 v.append(1);
148 const char *obj = reinterpret_cast<const char *>(&v);
149 const char *elem = reinterpret_cast<const char *>(v.data());
150 EXPECT_GE(elem, obj);
151 EXPECT_LT(elem, obj + sizeof(v));
152
153 for (int i = 0; i < 10; ++i)
154 v.append(i);
155 const char *heap_elem = reinterpret_cast<const char *>(v.data());
156 EXPECT_TRUE(heap_elem < obj or heap_elem >= obj + sizeof(v));
157}
158
159TEST(SmallVector, ElementLifetimesAreBalanced)
160{
161 ASSERT_EQ(Probe::live, 0);
162 {
164 v.emplace_back(1);
165 v.emplace_back(2);
166 EXPECT_EQ(Probe::live, 2);
167 v.emplace_back(3); // spill: old elements destroyed, new ones created
168 EXPECT_EQ(Probe::live, 3);
169 v.pop_back();
170 EXPECT_EQ(Probe::live, 2);
171 v.clear();
172 EXPECT_EQ(Probe::live, 0);
173 v.emplace_back(9);
174 EXPECT_EQ(Probe::live, 1);
175 }
176 EXPECT_EQ(Probe::live, 0); // destructor cleaned everything
177}
178
179TEST(SmallVector, FailedConstructorsCleanPartialElements)
180{
181 ASSERT_EQ(Throwing_Copy::live, 0);
182
183 {
184 Throwing_Copy value(1);
185 Throwing_Copy::reset(1);
186 EXPECT_THROW((SmallVector<Throwing_Copy, 2>(3, value)),
187 std::runtime_error);
188 EXPECT_EQ(Throwing_Copy::live, 1);
189 }
190 EXPECT_EQ(Throwing_Copy::live, 0);
191
192 Throwing_Copy::reset(1);
194 {Throwing_Copy(1), Throwing_Copy(2), Throwing_Copy(3)}),
195 std::runtime_error);
196 EXPECT_EQ(Throwing_Copy::live, 0);
197
198 {
199 std::vector<Throwing_Copy> src;
200 src.emplace_back(1);
201 src.emplace_back(2);
202 src.emplace_back(3);
203 ASSERT_EQ(Throwing_Copy::live, 3);
204 Throwing_Copy::reset(1);
205 EXPECT_THROW((SmallVector<Throwing_Copy, 2>(src.begin(), src.end())),
206 std::runtime_error);
207 EXPECT_EQ(Throwing_Copy::live, 3);
208 }
209 EXPECT_EQ(Throwing_Copy::live, 0);
210
211 {
213 src.emplace_back(1);
214 src.emplace_back(2);
215 src.emplace_back(3);
216 ASSERT_EQ(Throwing_Copy::live, 3);
217 Throwing_Copy::reset(1);
218 EXPECT_THROW((SmallVector<Throwing_Copy, 2>(src)), std::runtime_error);
219 EXPECT_EQ(Throwing_Copy::live, 3);
220 }
221 EXPECT_EQ(Throwing_Copy::live, 0);
222}
223
224TEST(SmallVector, MoveOnlyElementsAreSupported)
225{
227 v.append(std::make_unique<int>(1));
228 v.append(std::make_unique<int>(2));
229 v.append(std::make_unique<int>(3)); // spill with move-only type
230 ASSERT_EQ(v.size(), 3u);
231 EXPECT_EQ(*v[0], 1);
232 EXPECT_EQ(*v[2], 3);
233
234 std::unique_ptr<int> p = v.remove_last();
235 EXPECT_EQ(*p, 3);
236 EXPECT_EQ(v.size(), 2u);
237
238 SmallVector<std::unique_ptr<int>, 2> moved = std::move(v);
239 ASSERT_EQ(moved.size(), 2u);
240 EXPECT_EQ(*moved[1], 2);
241}
242
243TEST(SmallVector, CopySemanticsInBothModes)
244{
245 SmallVector<std::string, 4> inline_v = {"a", "b"};
246 SmallVector<std::string, 4> c1 = inline_v;
247 EXPECT_EQ(c1, inline_v);
248 c1[0] = "zzz";
249 EXPECT_EQ(inline_v[0], "a"); // deep copy
250
251 SmallVector<std::string, 2> heap_v = {"a", "b", "c", "d"};
252 EXPECT_FALSE(heap_v.is_small());
253 SmallVector<std::string, 2> c2 = heap_v;
254 EXPECT_EQ(c2, heap_v);
255
256 SmallVector<std::string, 2> small_src = {"s"};
257 c2 = small_src; // copy assignment: heap-mode target, inline-mode source
258 ASSERT_EQ(c2.size(), 1u);
259 EXPECT_EQ(c2[0], "s");
260}
261
262TEST(SmallVector, MoveSemanticsInBothModes)
263{
264 // Inline mode: elements are moved one by one; source is emptied.
265 SmallVector<std::string, 4> a = {"x", "y"};
266 SmallVector<std::string, 4> ma = std::move(a);
267 ASSERT_EQ(ma.size(), 2u);
268 EXPECT_EQ(ma[0], "x");
269 EXPECT_TRUE(a.is_empty());
270
271 // Heap mode: the buffer is stolen in O(1); source returns to inline.
272 SmallVector<std::string, 2> b = {"1", "2", "3"};
273 const std::string *heap_data = b.data();
274 SmallVector<std::string, 2> mb = std::move(b);
275 EXPECT_EQ(mb.data(), heap_data); // pointer stolen, no element moves
276 EXPECT_TRUE(b.is_empty());
277 EXPECT_TRUE(b.is_small());
278 b.append("reuse"); // source is reusable after move
279 EXPECT_EQ(b[0], "reuse");
280}
281
282TEST(SmallVector, InsertAndEraseAtPosition)
283{
284 SmallVector<int, 4> v = {1, 2, 4};
285 v.insert(2, 3); // {1, 2, 3, 4}
286 ASSERT_EQ(v.size(), 4u);
287 for (int i = 0; i < 4; ++i)
288 EXPECT_EQ(v[i], i + 1);
289
290 v.insert(0, 0); // {0, 1, 2, 3, 4} — forces spill
291 EXPECT_FALSE(v.is_small());
292 EXPECT_EQ(v[0], 0);
293 EXPECT_EQ(v[4], 4);
294
295 v.insert(v.size(), 5); // append via insert at end
296 EXPECT_EQ(v.get_last(), 5);
297
298 v.erase(0); // {1, 2, 3, 4, 5}
299 EXPECT_EQ(v.get_first(), 1);
300 v.erase(v.size() - 1); // {1, 2, 3, 4}
301 EXPECT_EQ(v.get_last(), 4);
302 EXPECT_EQ(v.size(), 4u);
303
304 EXPECT_THROW(v.insert(v.size() + 1, 9), std::out_of_range);
305 EXPECT_THROW(v.erase(v.size()), std::out_of_range);
306}
307
308TEST(SmallVector, CheckedAndUncheckedAccess)
309{
310 SmallVector<int, 4> v = {10, 20};
311 EXPECT_EQ(v[1], 20);
312 EXPECT_EQ(v(0), 10);
313 EXPECT_THROW((void) v[2], std::out_of_range);
314
315 const SmallVector<int, 4> &cv = v;
316 EXPECT_EQ(cv[0], 10);
317 EXPECT_THROW((void) cv[5], std::out_of_range);
318
320 EXPECT_THROW((void) e.get_first(), std::underflow_error);
321 EXPECT_THROW((void) e.get_last(), std::underflow_error);
322 EXPECT_THROW((void) e.remove_last(), std::underflow_error);
323 EXPECT_THROW(e.pop_back(), std::underflow_error);
324}
325
326TEST(SmallVector, IterationAndRangeFor)
327{
328 SmallVector<int, 3> v = {1, 2, 3, 4}; // heap mode
329 int sum = 0;
330 for (int x : v)
331 sum += x;
332 EXPECT_EQ(sum, 10);
333
334 for (int &x : v)
335 x *= 2;
336 EXPECT_EQ(v[3], 8);
337
338 EXPECT_EQ(static_cast<size_t>(v.end() - v.begin()), v.size());
339}
340
341TEST(SmallVector, AlephConventionsEmptyClearTraverse)
342{
343 SmallVector<int, 4> v = {1, 2, 3};
344
345 int sum = 0;
346 EXPECT_TRUE(v.traverse([&sum] (int &x) { sum += x; return true; }));
347 EXPECT_EQ(sum, 6);
348
349 int visited = 0;
350 const SmallVector<int, 4> &cv = v;
351 EXPECT_FALSE(cv.traverse([&visited] (const int &x)
352 {
353 ++visited;
354 return x < 2;
355 }));
356 EXPECT_EQ(visited, 2);
357
358 v.empty(); // Aleph convention: empty() clears
359 EXPECT_TRUE(v.is_empty());
360 EXPECT_EQ(v.size(), 0u);
361}
362
363TEST(SmallVector, ReserveGrowsAndKeepsElements)
364{
365 SmallVector<int, 2> v = {7, 8};
366 EXPECT_TRUE(v.is_small());
367 v.reserve(100);
368 EXPECT_FALSE(v.is_small());
369 EXPECT_GE(v.capacity(), 100u);
370 EXPECT_EQ(v[0], 7);
371 EXPECT_EQ(v[1], 8);
372
373 const int *p = v.data();
374 for (int i = 0; i < 90; ++i)
375 v.append(i);
376 EXPECT_EQ(v.data(), p); // no reallocation within reserved capacity
377}
378
379TEST(SmallVector, ReserveDetectsCapacityOverflow)
380{
382 EXPECT_THROW(v.reserve(static_cast<size_t>(-1)), std::overflow_error);
383 EXPECT_TRUE(v.is_empty());
384 EXPECT_TRUE(v.is_small());
385}
386
387TEST(SmallVector, RangeConstructorAndFillConstructor)
388{
389 const std::vector<int> src = {5, 6, 7, 8, 9};
390 SmallVector<int, 2> v(src.begin(), src.end());
391 ASSERT_EQ(v.size(), 5u);
392 EXPECT_EQ(v[4], 9);
393
395 ASSERT_EQ(f.size(), 3u);
396 EXPECT_EQ(f[2], "ho");
397 EXPECT_TRUE(f.is_small());
398}
399
400TEST(SmallVector, EqualityAcrossDifferentInlineCapacities)
401{
402 SmallVector<int, 2> a = {1, 2, 3};
403 SmallVector<int, 8> b = {1, 2, 3};
404 EXPECT_TRUE(a == b);
405 b.append(4);
406 EXPECT_TRUE(a != b);
407}
408
409TEST(SmallVector, SwapInAllModeCombinations)
410{
411 // heap <-> heap: O(1) pointer swap.
412 SmallVector<int, 2> h1 = {1, 2, 3};
413 SmallVector<int, 2> h2 = {9, 8, 7, 6};
414 const int *p1 = h1.data();
415 const int *p2 = h2.data();
416 h1.swap(h2);
417 EXPECT_EQ(h1.data(), p2);
418 EXPECT_EQ(h2.data(), p1);
419 EXPECT_EQ(h1.size(), 4u);
420 EXPECT_EQ(h2.size(), 3u);
421
422 // inline <-> heap.
423 SmallVector<int, 4> s1 = {1};
424 SmallVector<int, 4> s2 = {9, 8, 7, 6, 5};
425 s1.swap(s2);
426 EXPECT_EQ(s1.size(), 5u);
427 EXPECT_EQ(s1[0], 9);
428 EXPECT_EQ(s2.size(), 1u);
429 EXPECT_EQ(s2[0], 1);
430}
431
432TEST(SmallVector, AppendRangeTrivialTypeStaysInlineThenSpillsCorrectly)
433{
434 // char is trivially copyable: append_range should take the memcpy path.
435 const char src[] = {'a', 'b', 'c', 'd', 'e', 'f'};
436
438 v.append_range(src, 3);
439 EXPECT_TRUE(v.is_small());
440 ASSERT_EQ(v.size(), 3u);
441 for (size_t i = 0; i < 3; ++i)
442 EXPECT_EQ(v[i], src[i]);
443
444 v.append_range(src + 3, 1); // exactly fills the inline capacity (4)
445 EXPECT_TRUE(v.is_small());
446 ASSERT_EQ(v.size(), 4u);
447
448 v.append_range(src + 4, 2); // spills to heap
449 EXPECT_FALSE(v.is_small());
450 ASSERT_EQ(v.size(), 6u);
451 for (size_t i = 0; i < 6; ++i)
452 EXPECT_EQ(v[i], src[i]); // values preserved across the spill
453}
454
455TEST(SmallVector, AppendRangeOfZeroIsANoOp)
456{
457 SmallVector<int, 4> v = {1, 2, 3};
458 v.append_range(nullptr, 0); // count == 0: first is never read
459 EXPECT_EQ(v.size(), 3u);
460 EXPECT_TRUE(v.is_small());
461}
462
463TEST(SmallVector, AppendRangeRejectsNullSourceWhenCountIsPositive)
464{
465 SmallVector<int, 4> v = {1, 2, 3};
466 EXPECT_THROW(v.append_range(nullptr, 1), std::invalid_argument);
467 // Rejected before touching the vector: size/contents unchanged.
468 EXPECT_EQ(v.size(), 3u);
469 EXPECT_EQ(v[0], 1);
470 EXPECT_EQ(v[1], 2);
471 EXPECT_EQ(v[2], 3);
472}
473
474TEST(SmallVector, AppendRangeSingleCallGrowsAtMostOnce)
475{
476 // Appending a range larger than the remaining inline capacity in one
477 // append_range() call should grow exactly once to fit the whole range,
478 // not incrementally (verified indirectly: capacity right after the call
479 // already accommodates the full new size without a second, separate
480 // growth having been needed -- consistent with a single `grow()` call).
481 SmallVector<int, 2> v = {1};
482 const int src[] = {2, 3, 4, 5, 6};
483 v.append_range(src, 5);
484 ASSERT_EQ(v.size(), 6u);
485 EXPECT_GE(v.capacity(), 6u);
486 for (size_t i = 0; i < 5; ++i)
487 EXPECT_EQ(v[i + 1], src[i]);
488}
489
490TEST(SmallVector, AppendRangeNonTrivialTypeKeepsLifetimesBalanced)
491{
492 ASSERT_EQ(Probe::live, 0);
493 {
494 Probe src[3] = {Probe(1), Probe(2), Probe(3)};
495 EXPECT_EQ(Probe::live, 3);
496
498 v.append_range(src, 3); // spills: goes through the placement-new path
499 EXPECT_EQ(Probe::live, 6); // 3 sources + 3 copies
500 ASSERT_EQ(v.size(), 3u);
501 EXPECT_EQ(v[0].value, 1);
502 EXPECT_EQ(v[1].value, 2);
503 EXPECT_EQ(v[2].value, 3);
504 }
505 EXPECT_EQ(Probe::live, 0); // both the sources and the copies were destroyed
506}
507
508TEST(SmallVector, AppendRangeFailedCopyLeavesSizeUnchangedAndNoLeak)
509{
510 ASSERT_EQ(Throwing_Copy::live, 0);
511
512 Throwing_Copy src[4] = {Throwing_Copy(1), Throwing_Copy(2),
513 Throwing_Copy(3), Throwing_Copy(4)};
514 {
515 SmallVector<Throwing_Copy, 8> v; // stays inline: exercises the
516 // placement-new-loop branch, not a
517 // reallocation-triggered one.
518 Throwing_Copy::reset(2); // 3rd element's copy throws
519 EXPECT_THROW(v.append_range(src, 4), std::runtime_error);
520 // Strong guarantee: no partially-copied elements remain visible.
521 EXPECT_EQ(v.size(), 0u);
522 EXPECT_EQ(Throwing_Copy::live, 4); // only the 4 sources are still alive
523 }
524 Throwing_Copy::reset(-1);
525 EXPECT_EQ(Throwing_Copy::live, 4);
526}
size_t size_t int32_t value
Definition ca-c-api.h:116
Contiguous dynamic array with N elements of inline storage.
size_t capacity() const noexcept
Return the current capacity (inline or heap). O(1).
size_t size() const noexcept
Return the number of stored elements. O(1).
void reserve(const size_t cap)
Reserve capacity for at least cap elements.
T & get_last()
Last element (checked).
T & emplace_back(Args &&...args)
Construct an element in place at the end.
void append_range(const T *first, const size_t count)
Append count copies from [first, first + count), in order.
T & append(const T &item)
Append a copy of item.
void empty() noexcept
Destroy all elements (Aleph convention).
iterator end() noexcept
Iterator past the last element. O(1).
T * data() noexcept
Pointer to the contiguous element storage. O(1).
bool traverse(Operation operation)
Traverse elements in order while operation returns true.
void swap(SmallVector &v) noexcept(std::is_nothrow_move_constructible_v< T >)
Swap contents with v.
bool is_small() const noexcept
Return true while the elements still live in the inline buffer. O(1).
T remove_last()
Remove and return the last element.
T & get_first()
First element (checked).
T & insert(size_t pos, T item)
Insert an element at position pos, shifting the tail right.
void pop_back()
Remove the last element (STL style).
iterator begin() noexcept
Iterator to the first element. O(1).
void erase(const size_t pos)
Remove the element at position pos, shifting the tail left.
bool is_empty() const noexcept
Return true if no elements are stored. O(1).
void clear() noexcept
Destroy all elements. Alias of empty(). Capacity is kept.
#define TEST(name)
size_t blossom_maximum_cardinality_matching(const GT &g, DynDlist< typename GT::Arc * > &matching, SA sa=SA())
Alias of compute_maximum_cardinality_general_matching().
Definition Blossom.H:466
and
Check uniqueness with explicit hash + equality functors.
STL namespace.
Dynamic array with inline storage (Aleph::SmallVector).