1// Copyright (C) 2023 Christian Mazakas
2// Copyright (C) 2023 Joaquin M Lopez Munoz
3// Distributed under the Boost Software License, Version 1.0. (See accompanying
4// file LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt)
5
6#include "helpers.hpp"
7
8#include <boost/unordered/concurrent_flat_map.hpp>
9#include <boost/unordered/concurrent_flat_set.hpp>
10
11#if defined(__clang__) && defined(__has_warning)
12
13#if __has_warning("-Wself-assign-overloaded")
14#pragma clang diagnostic ignored "-Wself-assign-overloaded"
15#endif
16
17#if __has_warning("-Wself-move")
18#pragma clang diagnostic ignored "-Wself-move"
19#endif
20
21#endif /* defined(__clang__) && defined(__has_warning) */
22
23#if defined(BOOST_GCC) && BOOST_GCC >= 130000
24#pragma GCC diagnostic push
25#pragma GCC diagnostic ignored "-Wself-move"
26#endif
27
28test::seed_t initialize_seed{2762556623};
29
30using test::default_generator;
31using test::limited_range;
32using test::sequential;
33
34using hasher = stateful_hash;
35using key_equal = stateful_key_equal;
36
37using map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
38 key_equal, stateful_allocator<std::pair<raii const, raii> > >;
39
40using set_type = boost::unordered::concurrent_flat_set<raii, hasher,
41 key_equal, stateful_allocator<raii> >;
42
43using fancy_map_type = boost::unordered::concurrent_flat_map<raii, raii, hasher,
44 key_equal, stateful_allocator2<std::pair<raii const, raii> > >;
45
46using fancy_set_type = boost::unordered::concurrent_flat_set<raii, hasher,
47 key_equal, stateful_allocator2<raii> >;
48
49map_type* test_map;
50set_type* test_set;
51fancy_map_type* fancy_test_map;
52fancy_set_type* fancy_test_set;
53
54std::initializer_list<map_type::value_type> map_init_list{
55 {raii{0}, raii{0}},
56 {raii{1}, raii{1}},
57 {raii{2}, raii{2}},
58 {raii{3}, raii{3}},
59 {raii{4}, raii{4}},
60 {raii{5}, raii{5}},
61 {raii{6}, raii{6}},
62 {raii{6}, raii{6}},
63 {raii{7}, raii{7}},
64 {raii{8}, raii{8}},
65 {raii{9}, raii{9}},
66 {raii{10}, raii{10}},
67 {raii{9}, raii{9}},
68 {raii{8}, raii{8}},
69 {raii{7}, raii{7}},
70 {raii{6}, raii{6}},
71 {raii{5}, raii{5}},
72 {raii{4}, raii{4}},
73 {raii{3}, raii{3}},
74 {raii{2}, raii{2}},
75 {raii{1}, raii{1}},
76 {raii{0}, raii{0}},
77};
78
79std::initializer_list<set_type::value_type> set_init_list{
80 raii{0},
81 raii{1},
82 raii{2},
83 raii{3},
84 raii{4},
85 raii{5},
86 raii{6},
87 raii{6},
88 raii{7},
89 raii{8},
90 raii{9},
91 raii{10},
92 raii{9},
93 raii{8},
94 raii{7},
95 raii{6},
96 raii{5},
97 raii{4},
98 raii{3},
99 raii{2},
100 raii{1},
101 raii{0},
102};
103
104auto test_map_and_init_list=std::make_pair(x&: test_map,y&: map_init_list);
105auto test_set_and_init_list=std::make_pair(x&: test_set,y&: set_init_list);
106
107template <class T,bool POCCA, bool POCMA>
108struct poca_allocator: fancy_allocator<T>
109{
110 using super = fancy_allocator<T>;
111 using pointer = typename super::pointer;
112 using propagate_on_container_copy_assignment =
113 std::integral_constant<bool, POCCA>;
114 using propagate_on_container_move_assignment =
115 std::integral_constant<bool, POCMA>;
116
117 int x_ = -1;
118
119 template <class U> struct rebind
120 {
121 typedef poca_allocator<U, POCCA, POCMA> other;
122 };
123
124 poca_allocator() = default;
125 poca_allocator(poca_allocator const&) = default;
126 poca_allocator(poca_allocator &&) = default;
127
128 poca_allocator(int const x) : x_{x} {}
129
130 poca_allocator& operator=(poca_allocator const& rhs)
131 {
132 if (this != &rhs) {
133 super::operator=(rhs);
134 x_ = rhs.x_;
135 }
136 return *this;
137 }
138
139 template <class U> poca_allocator(
140 poca_allocator<U, POCCA, POCMA> const& rhs) :
141 super{rhs}, x_{rhs.x_}
142 {
143 }
144
145 pointer allocate(std::size_t n)
146 {
147 auto p = super::allocate(n + 1);
148 reinterpret_cast<char&>(*p) = static_cast<char>(x_);
149 return p + std::ptrdiff_t(1);
150 }
151
152 void deallocate(pointer p, std::size_t n)
153 {
154 p = p + std::ptrdiff_t(-1);
155 BOOST_TEST_EQ(reinterpret_cast<char&>(*p), static_cast<char>(x_));
156 super::deallocate(p, n + 1);
157 }
158
159 bool operator==(poca_allocator const& rhs) const { return x_ == rhs.x_; }
160 bool operator!=(poca_allocator const& rhs) const { return x_ != rhs.x_; }
161};
162
163template <class T>
164struct pocca_allocator: poca_allocator<T, true, false>
165{
166 pocca_allocator() = default;
167 pocca_allocator(pocca_allocator const&) = default;
168 pocca_allocator(pocca_allocator &&) = default;
169 using poca_allocator<T, true, false>::poca_allocator;
170
171 pocca_allocator& operator=(pocca_allocator const&) = default;
172};
173
174template <class T>
175struct pocma_allocator: poca_allocator<T, false, true>
176{
177 pocma_allocator() = default;
178 pocma_allocator(pocma_allocator const&) = default;
179 pocma_allocator(pocma_allocator &&) = default;
180 using poca_allocator<T, false, true>::poca_allocator;
181
182 pocma_allocator& operator=(pocma_allocator const&) = default;
183};
184
185namespace {
186 template <class X, class GF>
187 void copy_assign(X*, GF gen_factory, test::random_generator rg)
188 {
189 using value_type = typename X::value_type;
190 static constexpr auto value_type_cardinality =
191 value_cardinality<value_type>::value;
192 using allocator_type = typename X::allocator_type;
193
194 auto gen = gen_factory.template get<X>();
195 auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
196 auto reference_cont = reference_container<X>(values.begin(), values.end());
197
198 // lhs empty, rhs empty
199 {
200 raii::reset_counts();
201
202 X x(0, hasher(1), key_equal(2), allocator_type(3));
203
204 thread_runner(values, [&x](boost::span<value_type> s) {
205 (void)s;
206
207 X y;
208
209 BOOST_TEST(x.empty());
210 BOOST_TEST(y.empty());
211
212 y = x;
213
214 BOOST_TEST_EQ(x.hash_function(), y.hash_function());
215 BOOST_TEST_EQ(x.key_eq(), y.key_eq());
216 BOOST_TEST(x.get_allocator() != y.get_allocator());
217 });
218
219 BOOST_TEST_EQ(raii::destructor, 0u);
220 BOOST_TEST_EQ(raii::copy_assignment, 0u);
221 BOOST_TEST_EQ(raii::move_assignment, 0u);
222 BOOST_TEST_EQ(raii::copy_constructor, 0u);
223 }
224
225 // lhs non-empty, rhs empty
226 {
227 raii::reset_counts();
228
229 X x(0, hasher(1), key_equal(2), allocator_type(3));
230
231 auto const old_size = reference_cont.size();
232
233 thread_runner(values, [&x, &values](boost::span<value_type> s) {
234 (void)s;
235
236 X y(values.size());
237 for (auto const& v : values) {
238 y.insert(v);
239 }
240
241 BOOST_TEST(x.empty());
242 BOOST_TEST(!y.empty());
243
244 y = x;
245
246 BOOST_TEST_EQ(x.hash_function(), y.hash_function());
247 BOOST_TEST_EQ(x.key_eq(), y.key_eq());
248 BOOST_TEST(x.get_allocator() != y.get_allocator());
249
250 BOOST_TEST(y.empty());
251 });
252
253 BOOST_TEST_EQ(
254 raii::destructor, num_threads * (value_type_cardinality * old_size));
255 BOOST_TEST_EQ(raii::copy_assignment, 0u);
256 BOOST_TEST_EQ(raii::move_assignment, 0u);
257 BOOST_TEST_EQ(
258 raii::copy_constructor,
259 num_threads * value_type_cardinality * reference_cont.size());
260 }
261 check_raii_counts();
262
263 // lhs empty, rhs non-empty
264 {
265 raii::reset_counts();
266
267 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
268 for (auto const& v : values) {
269 x.insert(v);
270 }
271
272 auto const old_cc = +raii::copy_constructor;
273
274 thread_runner(
275 values, [&x, &reference_cont](boost::span<value_type> s) {
276 (void)s;
277
278 X y;
279
280 BOOST_TEST(!x.empty());
281 BOOST_TEST(y.empty());
282
283 y = x;
284
285 BOOST_TEST_EQ(x.hash_function(), y.hash_function());
286 BOOST_TEST_EQ(x.key_eq(), y.key_eq());
287 BOOST_TEST(x.get_allocator() != y.get_allocator());
288
289 test_matches_reference(y, reference_cont);
290 });
291
292 BOOST_TEST_EQ(
293 raii::destructor, num_threads * value_type_cardinality * x.size());
294 BOOST_TEST_EQ(raii::copy_assignment, 0u);
295 BOOST_TEST_EQ(raii::move_assignment, 0u);
296 BOOST_TEST_EQ(
297 raii::copy_constructor,
298 old_cc + (num_threads * value_type_cardinality * x.size()));
299 }
300 check_raii_counts();
301
302 // lhs non-empty, rhs non-empty
303 {
304 raii::reset_counts();
305
306 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
307 for (auto const& v : values) {
308 x.insert(v);
309 }
310
311 auto const old_size = x.size();
312 auto const old_cc = +raii::copy_constructor;
313
314 thread_runner(values, [&x, &values](boost::span<value_type> s) {
315 (void)s;
316
317 X y(values.size());
318 for (auto const& v : values) {
319 y.insert(v);
320 }
321
322 BOOST_TEST(!x.empty());
323 BOOST_TEST(!y.empty());
324
325 y = x;
326
327 BOOST_TEST_EQ(x.hash_function(), y.hash_function());
328 BOOST_TEST_EQ(x.key_eq(), y.key_eq());
329 BOOST_TEST(x.get_allocator() != y.get_allocator());
330 });
331
332 BOOST_TEST_EQ(
333 raii::destructor, 2 * num_threads * value_type_cardinality * old_size);
334 BOOST_TEST_EQ(raii::copy_assignment, 0u);
335 BOOST_TEST_EQ(raii::move_assignment, 0u);
336 BOOST_TEST_EQ(
337 raii::copy_constructor,
338 old_cc + (2 * num_threads * value_type_cardinality * x.size()));
339 }
340 check_raii_counts();
341
342 // self-assign
343 {
344 raii::reset_counts();
345
346 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
347 for (auto const& v : values) {
348 x.insert(v);
349 }
350
351 auto const old_cc = +raii::copy_constructor;
352
353 thread_runner(
354 values, [&x, &reference_cont](boost::span<value_type> s) {
355 (void)s;
356
357 BOOST_TEST(!x.empty());
358
359 x = x;
360
361 BOOST_TEST_EQ(x.hash_function(), hasher(1));
362 BOOST_TEST_EQ(x.key_eq(), key_equal(2));
363 BOOST_TEST(x.get_allocator() == allocator_type(3));
364
365 test_matches_reference(x, reference_cont);
366 });
367
368 BOOST_TEST_EQ(raii::destructor, 0u);
369 BOOST_TEST_EQ(raii::copy_assignment, 0u);
370 BOOST_TEST_EQ(raii::move_assignment, 0u);
371 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
372 }
373 check_raii_counts();
374
375 // propagation
376 {
377 using pocca_container_type = replace_allocator<X, pocca_allocator>;
378 using pocca_allocator_type =
379 typename pocca_container_type::allocator_type;
380
381 raii::reset_counts();
382
383 pocca_container_type x(
384 values.size(), hasher(1), key_equal(2), pocca_allocator_type(3));
385 for (auto const& v : values) {
386 x.insert(v);
387 }
388
389 auto const old_size = x.size();
390 auto const old_cc = +raii::copy_constructor;
391
392 thread_runner(values, [&x, &values](boost::span<value_type> s) {
393 (void)s;
394
395 pocca_container_type y(values.size());
396 for (auto const& v : values) {
397 y.insert(v);
398 }
399
400 BOOST_TEST(!x.empty());
401 BOOST_TEST(!y.empty());
402
403 BOOST_TEST(x.get_allocator() != y.get_allocator());
404
405 y = x;
406
407 BOOST_TEST_EQ(x.hash_function(), y.hash_function());
408 BOOST_TEST_EQ(x.key_eq(), y.key_eq());
409 BOOST_TEST(x.get_allocator() == y.get_allocator());
410 });
411
412 BOOST_TEST_EQ(
413 raii::destructor, 2 * num_threads * value_type_cardinality * old_size);
414 BOOST_TEST_EQ(raii::copy_assignment, 0u);
415 BOOST_TEST_EQ(raii::move_assignment, 0u);
416 BOOST_TEST_EQ(
417 raii::copy_constructor,
418 old_cc + (2 * num_threads * value_type_cardinality * x.size()));
419 }
420 check_raii_counts();
421 }
422
423 template <class X, class GF>
424 void move_assign(X*, GF gen_factory, test::random_generator rg)
425 {
426 using value_type = typename X::value_type;
427 static constexpr auto value_type_cardinality =
428 value_cardinality<value_type>::value;
429 using allocator_type = typename X::allocator_type;
430
431 using pocma_container_type = replace_allocator<X, pocma_allocator>;
432 using pocma_allocator_type = typename pocma_container_type::allocator_type;
433
434 auto gen = gen_factory.template get<X>();
435
436 BOOST_STATIC_ASSERT(
437 std::is_nothrow_move_assignable<
438 replace_allocator<X, std::allocator> >::value);
439
440 BOOST_STATIC_ASSERT(
441 !std::is_nothrow_move_assignable<
442 replace_allocator<X, stateful_allocator> >::value);
443
444 auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
445 auto reference_cont = reference_container<X>(values.begin(), values.end());
446
447 // move assignment has more complex requirements than copying
448 // equal allocators:
449 // lhs empty, rhs non-empty
450 // lhs non-empty, rhs empty
451 // lhs non-empty, rhs non-empty
452 //
453 // unequal allocators:
454 // lhs non-empty, rhs non-empty
455 //
456 // pocma
457 // self move-assign
458
459 // lhs empty, rhs empty
460 {
461 raii::reset_counts();
462
463 X x(0, hasher(1), key_equal(2), allocator_type(3));
464
465 std::atomic<unsigned> num_transfers{0};
466
467 thread_runner(
468 values, [&x, &num_transfers](boost::span<value_type> s) {
469 (void)s;
470
471 X y(0, hasher(2), key_equal(1), allocator_type(3));
472
473 BOOST_TEST(x.empty());
474 BOOST_TEST(y.empty());
475 BOOST_TEST(x.get_allocator() == y.get_allocator());
476
477 y = std::move(x);
478 if (y.hash_function() == hasher(1)) {
479 ++num_transfers;
480 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
481 } else {
482 BOOST_TEST_EQ(y.hash_function(), hasher(2));
483 BOOST_TEST_EQ(y.key_eq(), key_equal(1));
484 }
485
486 BOOST_TEST_EQ(x.hash_function(), hasher(2));
487 BOOST_TEST_EQ(x.key_eq(), key_equal(1));
488 BOOST_TEST(x.get_allocator() == y.get_allocator());
489 });
490
491 BOOST_TEST_EQ(num_transfers, 1u);
492
493 BOOST_TEST_EQ(raii::destructor, 0u);
494 BOOST_TEST_EQ(raii::copy_assignment, 0u);
495 BOOST_TEST_EQ(raii::move_assignment, 0u);
496 BOOST_TEST_EQ(raii::copy_constructor, 0u);
497 }
498
499 // lhs non-empty, rhs empty
500 {
501 raii::reset_counts();
502
503 X x(0, hasher(1), key_equal(2), allocator_type(3));
504
505 std::atomic<unsigned> num_transfers{0};
506
507 thread_runner(
508 values, [&x, &values, &num_transfers](boost::span<value_type> s) {
509 (void)s;
510
511 X y(values.size(), hasher(2), key_equal(1), allocator_type(3));
512 for (auto const& v : values) {
513 y.insert(v);
514 }
515
516 BOOST_TEST(x.empty());
517 BOOST_TEST(!y.empty());
518 BOOST_TEST(x.get_allocator() == y.get_allocator());
519
520 y = std::move(x);
521 if (y.hash_function() == hasher(1)) {
522 ++num_transfers;
523 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
524 } else {
525 BOOST_TEST_EQ(y.hash_function(), hasher(2));
526 BOOST_TEST_EQ(y.key_eq(), key_equal(1));
527 }
528
529 BOOST_TEST_EQ(x.hash_function(), hasher(2));
530 BOOST_TEST_EQ(x.key_eq(), key_equal(1));
531 BOOST_TEST(x.get_allocator() == y.get_allocator());
532
533 BOOST_TEST(y.empty());
534 });
535
536 BOOST_TEST_EQ(num_transfers, 1u);
537
538 BOOST_TEST_EQ(
539 raii::destructor, num_threads * value_type_cardinality * reference_cont.size());
540 BOOST_TEST_EQ(raii::copy_assignment, 0u);
541 BOOST_TEST_EQ(raii::move_assignment, 0u);
542 BOOST_TEST_EQ(
543 raii::copy_constructor,
544 num_threads * value_type_cardinality * reference_cont.size());
545 }
546 check_raii_counts();
547
548 // lhs empty, rhs non-empty
549 {
550 raii::reset_counts();
551
552 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
553 for (auto const& v : values) {
554 x.insert(v);
555 }
556
557 auto const old_cc = +raii::copy_constructor;
558 auto const old_mc = +raii::move_constructor;
559 std::atomic<unsigned> num_transfers{0};
560
561 thread_runner(values,
562 [&x, &reference_cont, &num_transfers](boost::span<value_type> s) {
563 (void)s;
564
565 X y(allocator_type(3));
566
567 BOOST_TEST(y.empty());
568 BOOST_TEST(x.get_allocator() == y.get_allocator());
569
570 y = std::move(x);
571 if (!y.empty()) {
572 ++num_transfers;
573 test_matches_reference(y, reference_cont);
574
575 BOOST_TEST_EQ(y.hash_function(), hasher(1));
576 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
577 } else {
578 BOOST_TEST_EQ(y.hash_function(), hasher());
579 BOOST_TEST_EQ(y.key_eq(), key_equal());
580 }
581
582 BOOST_TEST(x.empty());
583
584 BOOST_TEST_EQ(x.hash_function(), hasher());
585 BOOST_TEST_EQ(x.key_eq(), key_equal());
586 BOOST_TEST(x.get_allocator() == y.get_allocator());
587 });
588
589 BOOST_TEST_EQ(num_transfers, 1u);
590
591 BOOST_TEST_EQ(
592 raii::destructor, value_type_cardinality * reference_cont.size());
593 BOOST_TEST_EQ(raii::copy_assignment, 0u);
594 BOOST_TEST_EQ(raii::move_assignment, 0u);
595 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
596 BOOST_TEST_EQ(raii::move_constructor, old_mc);
597 }
598 check_raii_counts();
599
600 // lhs non-empty, rhs non-empty
601 {
602 raii::reset_counts();
603
604 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
605 for (auto const& v : values) {
606 x.insert(v);
607 }
608
609 auto const old_size = x.size();
610 auto const old_cc = +raii::copy_constructor;
611 auto const old_mc = +raii::move_constructor;
612
613 std::atomic<unsigned> num_transfers{0};
614
615 thread_runner(values, [&x, &values, &num_transfers, &reference_cont](
616 boost::span<value_type> s) {
617 (void)s;
618
619 X y(values.size(), hasher(2), key_equal(1), allocator_type(3));
620 for (auto const& v : values) {
621 y.insert(v);
622 }
623
624 BOOST_TEST(!y.empty());
625 BOOST_TEST(x.get_allocator() == y.get_allocator());
626
627 y = std::move(x);
628 if (y.hash_function() == hasher(1)) {
629 ++num_transfers;
630 test_matches_reference(y, reference_cont);
631
632 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
633 } else {
634 BOOST_TEST_EQ(y.hash_function(), hasher(2));
635 BOOST_TEST_EQ(y.key_eq(), key_equal(1));
636 }
637
638 BOOST_TEST(x.empty());
639
640 BOOST_TEST_EQ(x.hash_function(), hasher(2));
641 BOOST_TEST_EQ(x.key_eq(), key_equal(1));
642 BOOST_TEST(x.get_allocator() == y.get_allocator());
643 });
644
645 BOOST_TEST_EQ(num_transfers, 1u);
646
647 BOOST_TEST_EQ(
648 raii::destructor,
649 value_type_cardinality * old_size +
650 num_threads * value_type_cardinality * old_size);
651 BOOST_TEST_EQ(raii::copy_assignment, 0u);
652 BOOST_TEST_EQ(raii::move_assignment, 0u);
653 BOOST_TEST_EQ(raii::move_constructor, old_mc);
654 BOOST_TEST_EQ(
655 raii::copy_constructor,
656 old_cc + (num_threads * value_type_cardinality * reference_cont.size()));
657 }
658 check_raii_counts();
659
660 // lhs non-empty, rhs non-empty, unequal allocators, no propagation
661 {
662 raii::reset_counts();
663
664 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
665 for (auto const& v : values) {
666 x.insert(v);
667 }
668
669 auto const old_size = x.size();
670 auto const old_cc = +raii::copy_constructor;
671 auto const old_mc = +raii::move_constructor;
672
673 std::atomic<unsigned> num_transfers{0};
674
675 thread_runner(values, [&x, &values, &num_transfers, &reference_cont](
676 boost::span<value_type> s) {
677 (void)s;
678
679 X y(values.size(), hasher(2), key_equal(1), allocator_type(13));
680 for (auto const& v : values) {
681 y.insert(v);
682 }
683
684 BOOST_TEST(
685 !boost::allocator_is_always_equal<allocator_type>::type::value);
686
687 BOOST_TEST(!boost::allocator_propagate_on_container_move_assignment<
688 allocator_type>::type::value);
689
690 BOOST_TEST(!y.empty());
691 BOOST_TEST(x.get_allocator() != y.get_allocator());
692
693 y = std::move(x);
694 if (y.hash_function() == hasher(1)) {
695 ++num_transfers;
696 test_matches_reference(y, reference_cont);
697
698 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
699 } else {
700 BOOST_TEST_EQ(y.hash_function(), hasher(2));
701 BOOST_TEST_EQ(y.key_eq(), key_equal(1));
702 }
703
704 BOOST_TEST(x.empty());
705
706 BOOST_TEST_EQ(x.hash_function(), hasher(2));
707 BOOST_TEST_EQ(x.key_eq(), key_equal(1));
708 BOOST_TEST(x.get_allocator() != y.get_allocator());
709 });
710
711 BOOST_TEST_EQ(num_transfers, 1u);
712
713 BOOST_TEST_EQ(
714 raii::destructor,
715 2 * value_type_cardinality * old_size +
716 num_threads * value_type_cardinality * old_size);
717 BOOST_TEST_EQ(raii::copy_assignment, 0u);
718 BOOST_TEST_EQ(raii::move_assignment, 0u);
719 BOOST_TEST_EQ(
720 raii::move_constructor, old_mc + value_type_cardinality * old_size);
721 BOOST_TEST_EQ(
722 raii::copy_constructor,
723 old_cc + (num_threads * value_type_cardinality * reference_cont.size()));
724 }
725 check_raii_counts();
726
727 // lhs non-empty, rhs non-empty, pocma
728 {
729 raii::reset_counts();
730
731 pocma_container_type x(
732 values.size(), hasher(1), key_equal(2), pocma_allocator_type(3));
733 for (auto const& v : values) {
734 x.insert(v);
735 }
736
737 auto const old_size = x.size();
738 auto const old_cc = +raii::copy_constructor;
739 auto const old_mc = +raii::move_constructor;
740
741 std::atomic<unsigned> num_transfers{0};
742
743 thread_runner(values, [&x, &values, &num_transfers, &reference_cont](
744 boost::span<value_type> s) {
745 (void)s;
746
747 pocma_container_type y(
748 values.size(), hasher(2), key_equal(1), pocma_allocator_type(13));
749 for (auto const& v : values) {
750 y.insert(v);
751 }
752
753 BOOST_TEST(!y.empty());
754 BOOST_TEST(x.get_allocator() != y.get_allocator());
755
756 y = std::move(x);
757 if (y.hash_function() == hasher(1)) {
758 ++num_transfers;
759 test_matches_reference(y, reference_cont);
760
761 BOOST_TEST_EQ(y.key_eq(), key_equal(2));
762 } else {
763 BOOST_TEST_EQ(y.hash_function(), hasher(2));
764 BOOST_TEST_EQ(y.key_eq(), key_equal(1));
765 }
766
767 BOOST_TEST(x.empty());
768
769 BOOST_TEST_EQ(x.hash_function(), hasher(2));
770 BOOST_TEST_EQ(x.key_eq(), key_equal(1));
771 BOOST_TEST(x.get_allocator() == y.get_allocator());
772 });
773
774 BOOST_TEST_EQ(num_transfers, 1u);
775
776 BOOST_TEST_EQ(
777 raii::destructor,
778 value_type_cardinality * old_size +
779 num_threads * value_type_cardinality * old_size);
780 BOOST_TEST_EQ(raii::copy_assignment, 0u);
781 BOOST_TEST_EQ(raii::move_assignment, 0u);
782 BOOST_TEST_EQ(raii::move_constructor, old_mc);
783 BOOST_TEST_EQ(
784 raii::copy_constructor,
785 old_cc + (num_threads * value_type_cardinality * reference_cont.size()));
786 }
787 check_raii_counts();
788
789 // self-assign
790 {
791 raii::reset_counts();
792
793 X x(values.size(), hasher(1), key_equal(2), allocator_type(3));
794 for (auto const& v : values) {
795 x.insert(v);
796 }
797
798 auto const old_cc = +raii::copy_constructor;
799 auto const old_mc = +raii::move_constructor;
800
801 thread_runner(
802 values, [&x, &reference_cont](boost::span<value_type> s) {
803 (void)s;
804
805 x = std::move(x);
806
807 BOOST_TEST(!x.empty());
808
809 BOOST_TEST_EQ(x.hash_function(), hasher(1));
810 BOOST_TEST_EQ(x.key_eq(), key_equal(2));
811 BOOST_TEST(x.get_allocator() == allocator_type(3));
812
813 test_matches_reference(x, reference_cont);
814 });
815
816 BOOST_TEST_EQ(raii::destructor, 0u);
817 BOOST_TEST_EQ(raii::copy_assignment, 0u);
818 BOOST_TEST_EQ(raii::move_assignment, 0u);
819 BOOST_TEST_EQ(raii::move_constructor, old_mc);
820 BOOST_TEST_EQ(raii::copy_constructor, old_cc);
821 }
822 check_raii_counts();
823 }
824
825 template <class X, class IL>
826 void initializer_list_assign(std::pair<X*, IL> p)
827 {
828 using value_type = typename X::value_type;
829 static constexpr auto value_type_cardinality =
830 value_cardinality<value_type>::value;
831 using allocator_type = typename X::allocator_type;
832
833 auto init_list = p.second;
834 auto reference_cont = reference_container<X>(
835 init_list.begin(), init_list.end());
836 auto v = std::vector<value_type>(init_list.begin(), init_list.end());
837
838 {
839 raii::reset_counts();
840 X x(0, hasher(1), key_equal(2), allocator_type(3));
841
842 thread_runner(v, [&x, &init_list](boost::span<value_type> s) {
843 (void)s;
844 x = init_list;
845 });
846
847 test_matches_reference(x, reference_cont);
848 BOOST_TEST_EQ(x.hash_function(), hasher(1));
849 BOOST_TEST_EQ(x.key_eq(), key_equal(2));
850 BOOST_TEST(x.get_allocator() == allocator_type(3));
851
852 BOOST_TEST_EQ(
853 raii::copy_constructor,
854 num_threads * value_type_cardinality * x.size());
855 BOOST_TEST_EQ(
856 raii::destructor,
857 (num_threads - 1) * value_type_cardinality * x.size());
858 BOOST_TEST_EQ(raii::move_constructor, 0u);
859 BOOST_TEST_EQ(raii::copy_assignment, 0u);
860 BOOST_TEST_EQ(raii::move_assignment, 0u);
861 }
862 check_raii_counts();
863 }
864
865 template <class X, class GF>
866 void insert_and_assign(X*, GF gen_factory, test::random_generator rg)
867 {
868 using allocator_type = typename X::allocator_type;
869
870 auto gen = gen_factory.template get<X>();
871
872 std::thread t1, t2, t3;
873
874 boost::compat::latch start_latch(2), end_latch(2);
875
876 auto v1 = make_random_values(1024 * 16, [&] { return gen(rg); });
877 auto v2 = v1;
878 shuffle_values(v2);
879
880 auto reference_cont = reference_container<X>(v1.begin(), v1.end());
881
882 raii::reset_counts();
883 {
884 X c1(v1.size(), hasher(1), key_equal(2), allocator_type(3));
885 X c2(v2.size(), hasher(1), key_equal(2), allocator_type(3));
886
887 t1 = std::thread([&v1, &c1, &start_latch, &end_latch] {
888 start_latch.arrive_and_wait();
889 for (auto const& v : v1) {
890 c1.insert(v);
891 }
892 end_latch.arrive_and_wait();
893 });
894
895 t2 = std::thread([&v2, &c2, &end_latch, &start_latch] {
896 start_latch.arrive_and_wait();
897 for (auto const& v : v2) {
898 c2.insert(v);
899 }
900 end_latch.arrive_and_wait();
901 });
902
903 std::atomic<unsigned> num_assignments{0};
904 t3 = std::thread([&c1, &c2, &end_latch, &num_assignments] {
905 while (c1.empty() && c2.empty()) {
906 std::this_thread::sleep_for(rtime: std::chrono::microseconds(10));
907 }
908
909 do {
910 c1 = c2;
911 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(100));
912 c2 = c1;
913 std::this_thread::sleep_for(rtime: std::chrono::milliseconds(100));
914 ++num_assignments;
915 } while (!end_latch.try_wait());
916 });
917
918 t1.join();
919 t2.join();
920 t3.join();
921
922 BOOST_TEST_GT(num_assignments, 0u);
923
924 test_fuzzy_matches_reference(c1, reference_cont, rg);
925 test_fuzzy_matches_reference(c2, reference_cont, rg);
926 }
927 check_raii_counts();
928 }
929
930 template <class X, class GF>
931 void flat_move_assign(X*, GF gen_factory, test::random_generator rg)
932 {
933 using value_type = typename X::value_type;
934 static constexpr auto value_type_cardinality =
935 value_cardinality<value_type>::value;
936 using allocator_type = typename X::allocator_type;
937
938 auto gen = gen_factory.template get<X>();
939 auto values = make_random_values(1024 * 16, [&] { return gen(rg); });
940 auto reference_cont = reference_container<X>(values.begin(), values.end());
941
942 /*
943 * basically test that a temporary container is materialized and we
944 * move-assign from that
945 *
946 * we don't need to be super rigorous here because we already have tests for
947 * container assignment, we're just testing that a temporary is materialized
948 */
949
950 {
951 raii::reset_counts();
952
953 flat_container<X> flat(values.begin(), values.end(), values.size(),
954 hasher(1), key_equal(2), allocator_type(3));
955
956 X x(0, hasher(2), key_equal(1), allocator_type(3));
957
958 BOOST_TEST(flat.get_allocator() == x.get_allocator());
959
960 x = std::move(flat);
961
962 BOOST_TEST(flat.empty());
963 BOOST_TEST_EQ(x.size(), reference_cont.size());
964
965 test_fuzzy_matches_reference(x, reference_cont, rg);
966
967 BOOST_TEST_EQ(x.hash_function(), hasher(1));
968 BOOST_TEST_EQ(x.key_eq(), key_equal(2));
969
970 BOOST_TEST_EQ(
971 raii::copy_constructor, value_type_cardinality * reference_cont.size());
972 BOOST_TEST_EQ(raii::destructor, 0u);
973 BOOST_TEST_EQ(raii::move_constructor, 0u);
974 BOOST_TEST_EQ(raii::copy_assignment, 0u);
975 BOOST_TEST_EQ(raii::move_assignment, 0u);
976 }
977
978 check_raii_counts();
979
980 {
981 raii::reset_counts();
982
983 X x(values.begin(), values.end(), values.size(), hasher(1),
984 key_equal(2), allocator_type(3));
985
986 flat_container<X> flat(0, hasher(2), key_equal(1), allocator_type(3));
987
988 BOOST_TEST(flat.get_allocator() == x.get_allocator());
989
990 flat = std::move(x);
991
992 BOOST_TEST(x.empty());
993 BOOST_TEST_EQ(flat.size(), reference_cont.size());
994
995 BOOST_TEST_EQ(flat.hash_function(), hasher(1));
996 BOOST_TEST_EQ(flat.key_eq(), key_equal(2));
997
998 BOOST_TEST_EQ(
999 raii::copy_constructor, value_type_cardinality * reference_cont.size());
1000 BOOST_TEST_EQ(raii::destructor, 0u);
1001 BOOST_TEST_EQ(raii::move_constructor, 0u);
1002 BOOST_TEST_EQ(raii::copy_assignment, 0u);
1003 BOOST_TEST_EQ(raii::move_assignment, 0u);
1004 }
1005
1006 check_raii_counts();
1007
1008 {
1009 raii::reset_counts();
1010
1011 flat_container<X> flat(values.begin(), values.end(), values.size(),
1012 hasher(1), key_equal(2), allocator_type(3));
1013
1014 X x(0, hasher(2), key_equal(1), allocator_type(4));
1015
1016 BOOST_TEST(flat.get_allocator() != x.get_allocator());
1017
1018 x = std::move(flat);
1019
1020 BOOST_TEST(flat.empty());
1021 BOOST_TEST_EQ(x.size(), reference_cont.size());
1022
1023 test_fuzzy_matches_reference(x, reference_cont, rg);
1024
1025 BOOST_TEST_EQ(x.hash_function(), hasher(1));
1026 BOOST_TEST_EQ(x.key_eq(), key_equal(2));
1027
1028 BOOST_TEST_EQ(
1029 raii::copy_constructor, value_type_cardinality * reference_cont.size());
1030 BOOST_TEST_EQ(
1031 raii::destructor, value_type_cardinality * reference_cont.size());
1032 BOOST_TEST_EQ(
1033 raii::move_constructor, value_type_cardinality * reference_cont.size());
1034 BOOST_TEST_EQ(raii::copy_assignment, 0u);
1035 BOOST_TEST_EQ(raii::move_assignment, 0u);
1036 }
1037
1038 check_raii_counts();
1039
1040 {
1041 raii::reset_counts();
1042
1043 X x(values.begin(), values.end(), values.size(), hasher(1),
1044 key_equal(2), allocator_type(3));
1045
1046 flat_container<X> flat(0, hasher(2), key_equal(1), allocator_type(4));
1047
1048 BOOST_TEST(flat.get_allocator() != x.get_allocator());
1049
1050 flat = std::move(x);
1051
1052 BOOST_TEST(x.empty());
1053 BOOST_TEST_EQ(flat.size(), reference_cont.size());
1054
1055 BOOST_TEST_EQ(flat.hash_function(), hasher(1));
1056 BOOST_TEST_EQ(flat.key_eq(), key_equal(2));
1057
1058 BOOST_TEST_EQ(
1059 raii::copy_constructor, value_type_cardinality * reference_cont.size());
1060 BOOST_TEST_EQ(
1061 raii::destructor, value_type_cardinality * reference_cont.size());
1062 BOOST_TEST_EQ(
1063 raii::move_constructor, value_type_cardinality * reference_cont.size());
1064 BOOST_TEST_EQ(raii::copy_assignment, 0u);
1065 BOOST_TEST_EQ(raii::move_assignment, 0u);
1066 }
1067
1068 check_raii_counts();
1069 }
1070
1071} // namespace
1072
1073// clang-format off
1074UNORDERED_TEST(
1075 copy_assign,
1076 ((test_map)(test_set))
1077 ((value_type_generator_factory))
1078 ((default_generator)(sequential)(limited_range)))
1079
1080UNORDERED_TEST(
1081 move_assign,
1082 ((test_map)(test_set))
1083 ((value_type_generator_factory))
1084 ((default_generator)(sequential)(limited_range)))
1085
1086UNORDERED_TEST(
1087 initializer_list_assign,
1088 ((test_map_and_init_list)(test_set_and_init_list)))
1089
1090UNORDERED_TEST(
1091 insert_and_assign,
1092 ((test_map)(test_set))
1093 ((init_type_generator_factory))
1094 ((default_generator)(sequential)(limited_range)))
1095
1096UNORDERED_TEST(
1097 flat_move_assign,
1098 ((test_map)(test_set)(fancy_test_map)(fancy_test_set))
1099 ((init_type_generator_factory))
1100 ((default_generator)(sequential)(limited_range)))
1101// clang-format on
1102
1103RUN_TESTS()
1104

source code of boost/libs/unordered/test/cfoa/assign_tests.cpp