Coverage Report

Created: 2024-04-30 09:35

src/zserio/OptionalHolder.h
Line
Count
Source
1
#ifndef ZSERIO_OPTIONAL_HOLDER_H_INC
2
#define ZSERIO_OPTIONAL_HOLDER_H_INC
3
4
#include <cstddef>
5
#include <type_traits>
6
7
#include "zserio/CppRuntimeException.h"
8
#include "zserio/NoInit.h"
9
#include "zserio/Types.h"
10
#include "zserio/UniquePtr.h"
11
12
namespace zserio
13
{
14
15
/**
16
 * Helper type for specification of an unset optional holder.
17
 */
18
struct NullOptType
19
{
20
    /**
21
     * Explicit constructor from int.
22
     *
23
     * \see https://en.cppreference.com/w/cpp/utility/optional/nullopt_t
24
     */
25
    constexpr explicit NullOptType(int)
26
2
    {}
27
};
28
29
/**
30
 * Constant used to convenient specification of an unset optional holder.
31
 */
32
constexpr NullOptType NullOpt{int()};
33
34
/**
35
 * Helper type for specification of in-place construction.
36
 */
37
struct InPlaceT
38
{
39
    constexpr explicit InPlaceT() = default;
40
};
41
42
/**
43
 * Constant used as a marker for in-place construction.
44
 */
45
constexpr InPlaceT InPlace{};
46
47
namespace detail
48
{
49
50
/**
51
 * Base class for optional holders. Provides common interface for various types of optional holders.
52
 */
53
template <typename T, typename Derived>
54
class optional_holder_base
55
{
56
public:
57
    /**
58
     * Operator equality.
59
     *
60
     * \param other Other holder to compare.
61
     *
62
     * \return True when the other holder has same value as this. False otherwise.
63
     */
64
    bool operator==(const optional_holder_base& other) const
65
31
    {
66
31
        if (this == &other)
67
5
            return true;
68
69
26
        if (getDerived()->hasValue() != other.getDerived()->hasValue())
70
5
            return false;
71
72
21
        if (getDerived()->hasValue())
73
14
            return get() == other.get();
74
75
7
        return true;
76
21
    }
77
78
    /**
79
     * Operator less than.
80
     *
81
     * \param other Other holder to compare.
82
     *
83
     * \return True when the other holder has less value than this. False otherwise.
84
     */
85
    bool operator<(const optional_holder_base& other) const
86
10
    {
87
10
        if (getDerived()->hasValue() && 
other.getDerived()->hasValue()6
)
88
4
            return get() < other.get();
89
90
6
        return !getDerived()->hasValue() && 
other.getDerived()->hasValue()4
;
91
10
    }
92
93
    /**
94
     * Bool operator.
95
     *
96
     * Evaluates to true when this holder has assigned any value.
97
     */
98
    explicit operator bool() const noexcept
99
539
    {
100
539
        return getDerived()->hasValue();
101
539
    }
102
103
    /**
104
     * Dereference operator ->.
105
     *
106
     * \return Const reference to the assigned value.
107
     *
108
     * \throw CppRuntimeException when the holder is unset.
109
     */
110
    const T* operator->() const
111
4
    {
112
4
        return std::addressof(get());
113
4
    }
114
115
    /**
116
     * Dereference operator ->.
117
     *
118
     * \return Reference to the assigned value.
119
     *
120
     * \throw CppRuntimeException when the holder is unset.
121
     */
122
    T* operator->()
123
480
    {
124
480
        return std::addressof(get());
125
480
    }
126
127
    /**
128
     * Gets held value.
129
     *
130
     * \return Const reference to the assigned value.
131
     *
132
     * \throw CppRuntimeException when the holder is unset.
133
     */
134
    const T& value() const
135
48.7k
    {
136
48.7k
        return get();
137
48.7k
    }
138
139
    /**
140
     * Gets held value.
141
     *
142
     * \return Reference to the assigned value.
143
     *
144
     * \throw CppRuntimeException when the holder is unset.
145
     */
146
    T& value()
147
859
    {
148
859
        return get();
149
859
    }
150
151
protected:
152
    void checkHasValue() const
153
50.2k
    {
154
50.2k
        if (!getDerived()->hasValue())
155
14
            throwNonPresentException();
156
50.2k
    }
157
158
private:
159
    T& get()
160
1.33k
    {
161
1.33k
        return getDerived()->operator*();
162
1.33k
    }
163
164
    const T& get() const
165
48.8k
    {
166
48.8k
        return getDerived()->operator*();
167
48.8k
    }
168
169
    Derived* getDerived()
170
1.33k
    {
171
1.33k
        return static_cast<Derived*>(this);
172
1.33k
    }
173
174
    const Derived* getDerived() const
175
99.7k
    {
176
99.7k
        return static_cast<const Derived*>(this);
177
99.7k
    }
178
179
    /** Optimization which increases chances to inline checkHasValue(). */
180
    void throwNonPresentException() const
181
14
    {
182
14
        throw CppRuntimeException("Trying to access value of non-present optional field!");
183
14
    }
184
};
185
186
/**
187
 * Optional holder implementation that stores the object on heap.
188
 */
189
template <typename T, typename ALLOC>
190
class heap_optional_holder : public optional_holder_base<T, heap_optional_holder<T, ALLOC>>
191
{
192
public:
193
    using allocator_type = ALLOC;
194
    using allocator_traits = std::allocator_traits<allocator_type>;
195
    using value_type = T;
196
    using storage_type = zserio::unique_ptr<T, allocator_type>;
197
198
    /**
199
     * Getter for the allocator.
200
     *
201
     * \return Allocator that is used for the dynamic memory allocation.
202
     */
203
    allocator_type get_allocator() const
204
98
    {
205
98
        return m_storage.get_deleter().get_allocator();
206
98
    }
207
208
    /**
209
     * Empty constructor which creates an unset holder.
210
     *
211
     * \param allocator Allocator to be used to perform dynamic memory allocations.
212
     */
213
    explicit constexpr heap_optional_holder(const allocator_type& allocator = allocator_type()) noexcept :
214
            m_storage(nullptr, allocator)
215
45
    {}
216
217
    /**
218
     * Constructor from zserio::NullOpt constant to create an unset holder.
219
     *
220
     * \param allocator Allocator to be used to perform dynamic memory allocations.
221
     */
222
    constexpr heap_optional_holder(NullOptType, const allocator_type& allocator = allocator_type()) noexcept :
223
            m_storage(nullptr, allocator)
224
2
    {}
225
226
    /**
227
     * Constructor from a given value.
228
     *
229
     * \param value Value to store in the holder.
230
     * \param allocator Allocator to be used to perform dynamic memory allocations.
231
     */
232
    heap_optional_holder(const T& value, const allocator_type& allocator = allocator_type()) :
233
            m_storage(zserio::allocate_unique<T, allocator_type>(allocator, value))
234
2
    {}
235
236
    /**
237
     * Constructor from a given value passed by rvalue reference.
238
     *
239
     * \param value Value to store in the holder.
240
     * \param allocator Allocator to be used to perform dynamic memory allocations.
241
     */
242
    heap_optional_holder(T&& value, const allocator_type& allocator = allocator_type()) :
243
            m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::move(value)))
244
27
    {}
245
246
    // called from allocatorPropagatingCopy
247
    /**
248
     * Constructor from a given value passed by rvalue reference which prevents initialization.
249
     *
250
     * \param value Value to store in the holder.
251
     * \param allocator Allocator to be used to perform dynamic memory allocations.
252
     */
253
    template <typename U = T,
254
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
255
    heap_optional_holder(NoInitT, T&& value, const allocator_type& allocator = allocator_type()) :
256
            m_storage(zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(value)))
257
2
    {}
258
259
    /**
260
     * Constructor that initializes the value inplace by forwarding the arguments to the object's constructor.
261
     *
262
     * \param allocator Allocator to be used for dynamic memory allocations.
263
     * \param parameters Parameters for object's constructor.
264
     */
265
    template <typename... U>
266
    explicit heap_optional_holder(const allocator_type& allocator, U&&... parameters) :
267
            m_storage(zserio::allocate_unique<T, allocator_type>(allocator, std::forward<U>(parameters)...))
268
2
    {}
269
270
    /**
271
     * Destructor.
272
     */
273
98
    ~heap_optional_holder() = default;
274
275
    /**
276
     * Copy constructor.
277
     *
278
     * \param other Other holder to copy.
279
     */
280
    heap_optional_holder(const heap_optional_holder& other) :
281
            m_storage(copy_initialize(
282
                    other, allocator_traits::select_on_container_copy_construction(other.get_allocator())))
283
6
    {}
284
285
    /**
286
     * Copy constructor which prevents initialization.
287
     *
288
     * \param other Other holder to copy.
289
     */
290
    template <typename U = T,
291
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
292
    heap_optional_holder(NoInitT, const heap_optional_holder& other) :
293
            m_storage(copy_initialize(NoInit, other,
294
                    allocator_traits::select_on_container_copy_construction(other.get_allocator())))
295
2
    {}
296
297
    /**
298
     * Allocator-extended copy constructor.
299
     *
300
     * \param other Other holder to copy.
301
     * \param allocator Allocator to be used for dynamic memory allocations.
302
     */
303
    heap_optional_holder(const heap_optional_holder& other, const allocator_type& allocator) :
304
            m_storage(copy_initialize(other, allocator))
305
2
    {}
306
307
    /**
308
     * Move constructor.
309
     *
310
     * \param other Other holder to move.
311
     */
312
1
    heap_optional_holder(heap_optional_holder&& other) noexcept = default;
313
314
    /**
315
     * Move constructor which prevents initialization.
316
     *
317
     * \param other Other holder to move.
318
     */
319
    template <typename U = T,
320
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
321
    heap_optional_holder(NoInitT, heap_optional_holder&& other) :
322
            m_storage(move_initialize(NoInit, std::move(other), other.get_allocator()))
323
4
    {}
324
325
    /**
326
     * Allocator-extended move constructor.
327
     *
328
     * \param other Other holder to move.
329
     * \param allocator Allocator to be used for dynamic memory allocations.
330
     */
331
    heap_optional_holder(heap_optional_holder&& other, const allocator_type& allocator) :
332
            m_storage(move_initialize(std::move(other), allocator))
333
3
    {}
334
335
    /**
336
     * Assignment operator.
337
     *
338
     * \param other Other holder to copy-assign.
339
     *
340
     * \return Reference to the current holder.
341
     */
342
    heap_optional_holder& operator=(const heap_optional_holder& other)
343
5
    {
344
5
        if (this == &other)
345
2
            return *this;
346
347
3
        m_storage = copy_initialize(other,
348
3
                select_allocator(other.get_allocator(),
349
3
                        typename allocator_traits::propagate_on_container_copy_assignment()));
350
351
3
        return *this;
352
5
    }
353
354
    /**
355
     * Assignment operator which prevents initialization.
356
     *
357
     * \param other Other holder to copy-assign.
358
     *
359
     * \return Reference to the current holder.
360
     */
361
    template <typename U = T,
362
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
363
    heap_optional_holder& assign(NoInitT, const heap_optional_holder& other)
364
3
    {
365
3
        if (this == &other)
366
1
            return *this;
367
368
2
        m_storage = copy_initialize(NoInit, other,
369
2
                select_allocator(other.get_allocator(),
370
2
                        typename allocator_traits::propagate_on_container_copy_assignment()));
371
372
2
        return *this;
373
3
    }
374
375
    /**
376
     * Move assignment operator.
377
     *
378
     * \param other Other holder to move-assign.
379
     *
380
     * \return Reference to the current holder.
381
     */
382
    heap_optional_holder& operator=(heap_optional_holder&& other)
383
4
    {
384
4
        if (this == &other)
385
2
            return *this;
386
387
2
        m_storage = move_initialize(std::move(other),
388
2
                select_allocator(other.get_allocator(),
389
2
                        typename allocator_traits::propagate_on_container_move_assignment()));
390
391
2
        return *this;
392
4
    }
393
394
    /**
395
     * Move assignment operator which prevents initialization.
396
     *
397
     * \param other Other holder to move-assign.
398
     *
399
     * \return Reference to the current holder.
400
     */
401
    template <typename U = T,
402
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
403
    heap_optional_holder& assign(NoInitT, heap_optional_holder&& other)
404
5
    {
405
5
        if (this == &other)
406
2
            return *this;
407
408
3
        m_storage = move_initialize(NoInit, std::move(other),
409
3
                select_allocator(other.get_allocator(),
410
3
                        typename allocator_traits::propagate_on_container_move_assignment()));
411
412
3
        return *this;
413
5
    }
414
415
    /**
416
     * Assignment operator from value.
417
     *
418
     * \param value Value to assign.
419
     *
420
     * \return Reference to the current holder.
421
     */
422
    heap_optional_holder& operator=(const T& value)
423
8
    {
424
8
        set(value);
425
426
8
        return *this;
427
8
    }
428
429
    /**
430
     * Assignment operator from rvalue reference to value.
431
     *
432
     * \param value Value to move-assign.
433
     *
434
     * \return Reference to the current holder.
435
     */
436
    heap_optional_holder& operator=(T&& value)
437
7
    {
438
7
        set(std::move(value));
439
440
7
        return *this;
441
7
    }
442
443
    /**
444
     * Resets the current holder (switch to the unset state).
445
     */
446
    void reset() noexcept
447
17
    {
448
17
        m_storage.reset();
449
17
    }
450
451
    /**
452
     * Gets whether the holder has any value.
453
     *
454
     * \return True when this holder has assigned any value. False otherwise.
455
     */
456
    bool hasValue() const noexcept
457
208
    {
458
208
        return static_cast<bool>(m_storage);
459
208
    }
460
461
    /**
462
     * Dereference operator *.
463
     *
464
     * \return Const reference to the assigned value.
465
     *
466
     * \throw CppRuntimeException when the holder is unset.
467
     */
468
    const T& operator*() const
469
34
    {
470
34
        this->checkHasValue();
471
34
        return *m_storage.get();
472
34
    }
473
474
    /**
475
     * Dereference operator *.
476
     *
477
     * \return Reference to the assigned value.
478
     *
479
     * \throw CppRuntimeException when the holder is unset.
480
     */
481
    T& operator*()
482
76
    {
483
76
        this->checkHasValue();
484
76
        return *m_storage.get();
485
76
    }
486
487
private:
488
    allocator_type select_allocator(allocator_type other_allocator, std::true_type)
489
7
    {
490
7
        return other_allocator;
491
7
    }
492
493
    allocator_type select_allocator(allocator_type, std::false_type)
494
3
    {
495
3
        return get_allocator(); // current allocator
496
3
    }
497
498
    template <typename U = T>
499
    void set(U&& value)
500
15
    {
501
15
        reset();
502
15
        m_storage = zserio::allocate_unique<T, allocator_type>(get_allocator(), std::forward<U>(value));
503
15
    }
504
505
    static storage_type copy_initialize(const heap_optional_holder& other, const allocator_type& allocator)
506
11
    {
507
11
        if (other.hasValue())
508
9
            return zserio::allocate_unique<T, allocator_type>(allocator, *other);
509
2
        else
510
2
            return storage_type(nullptr, allocator);
511
11
    }
512
513
    static storage_type copy_initialize(
514
            NoInitT, const heap_optional_holder& other, const allocator_type& allocator)
515
4
    {
516
4
        if (other.hasValue())
517
2
            return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, *other);
518
2
        else
519
2
            return storage_type(nullptr, allocator);
520
4
    }
521
522
    static storage_type move_initialize(heap_optional_holder&& other, const allocator_type& allocator)
523
5
    {
524
5
        if (other.hasValue())
525
4
        {
526
4
            if (allocator == other.get_allocator())
527
2
                return std::move(other.m_storage);
528
529
2
            return zserio::allocate_unique<T, allocator_type>(allocator, std::move(*other));
530
4
        }
531
1
        else
532
1
        {
533
1
            return storage_type(nullptr, allocator);
534
1
        }
535
5
    }
536
537
    static storage_type move_initialize(NoInitT, heap_optional_holder&& other, const allocator_type& allocator)
538
7
    {
539
7
        if (other.hasValue())
540
4
        {
541
4
            if (allocator == other.get_allocator())
542
3
                return std::move(other.m_storage);
543
544
1
            return zserio::allocate_unique<T, allocator_type>(allocator, NoInit, std::move(*other));
545
4
        }
546
3
        else
547
3
        {
548
3
            return storage_type(nullptr, allocator);
549
3
        }
550
7
    }
551
552
    storage_type m_storage;
553
};
554
555
/**
556
 * In place storage for optional holder.
557
 */
558
template <typename T>
559
class in_place_storage
560
{
561
public:
562
    /**
563
     * Constructor.
564
     */
565
    in_place_storage() = default;
566
567
    /**
568
     * Destructor.
569
     */
570
    ~in_place_storage() = default;
571
572
    /** Copying is disabled. */
573
    /** \{ */
574
    in_place_storage(const in_place_storage&) = delete;
575
    in_place_storage& operator=(const in_place_storage&) = delete;
576
    /** \} */
577
578
    /** Move constructor is disabled. */
579
    in_place_storage(in_place_storage&& other) = delete;
580
581
    /**
582
     * Move assignment operator.
583
     *
584
     * \param other Other storage to move.
585
     *
586
     * \return Reference to the current storage.
587
     */
588
    in_place_storage& operator=(in_place_storage&& other)
589
16.3k
    {
590
16.3k
        new (&m_inPlace) T(std::move(*other.getObject()));
591
16.3k
        other.getObject()->~T(); // ensure that destructor of object in original storage is called
592
593
16.3k
        return *this;
594
16.3k
    }
595
596
    /**
597
     * Move assignment operator which prevents initialization.
598
     *
599
     * \param other Other storage to move.
600
     *
601
     * \return Reference to the current storage.
602
     */
603
    in_place_storage& assign(NoInitT, in_place_storage&& other)
604
10
    {
605
10
        new (&m_inPlace) T(NoInit, std::move(*other.getObject()));
606
10
        other.getObject()->~T(); // ensure that destructor of object in original storage is called
607
608
10
        return *this;
609
10
    }
610
611
    /**
612
     * Gets pointer to the underlying in-place memory.
613
     *
614
     * \return Pointer to the storage.
615
     */
616
    void* getStorage()
617
50.5k
    {
618
50.5k
        return &m_inPlace;
619
50.5k
    }
620
621
    /**
622
     * Gets pointer to the stored object.
623
     *
624
     * \return Pointer to the object.
625
     */
626
    T* getObject()
627
84.5k
    {
628
84.5k
        return reinterpret_cast<T*>(&m_inPlace);
629
84.5k
    }
630
631
    /**
632
     * Gets pointer to the stored object.
633
     *
634
     * \return Const pointer to the object.
635
     */
636
    const T* getObject() const
637
48.9k
    {
638
48.9k
        return reinterpret_cast<const T*>(&m_inPlace);
639
48.9k
    }
640
641
private:
642
    // Caution!
643
    // We use static constants as a WORKAROUND for a GCC 8/9 bug observed when cross-compiling with -m32 flag.
644
    // The compilers somehow spoils the aligned storage when alignof(T) is used directly as the template
645
    // argument which leads to "*** stack smashing detected ***" error (as observed in some language tests).
646
    static constexpr size_t SIZEOF_T = sizeof(T);
647
    static constexpr size_t ALIGNOF_T = alignof(T);
648
    using AlignedStorage = typename std::aligned_storage<SIZEOF_T, ALIGNOF_T>::type;
649
    AlignedStorage m_inPlace;
650
};
651
652
/**
653
 * Optional holder implementation for Zserio which allows usage of in place storage.
654
 */
655
template <typename T>
656
class inplace_optional_holder : public optional_holder_base<T, inplace_optional_holder<T>>
657
{
658
public:
659
    using value_type = T;
660
661
    /**
662
     * Empty constructor which creates an unset holder.
663
     */
664
66.5k
    constexpr inplace_optional_holder() noexcept = default;
665
666
    /**
667
     * Constructor from zserio::NullOpt constant to create an unset holder.
668
     */
669
    constexpr inplace_optional_holder(NullOptType) noexcept
670
301
    {}
671
672
    /**
673
     * Constructor from a given value.
674
     *
675
     * \param value Value to store in the holder.
676
     */
677
    inplace_optional_holder(const T& value)
678
42
    {
679
42
        new (m_storage.getStorage()) T(value);
680
42
        m_hasValue = true;
681
42
    }
682
683
    /**
684
     * Constructor from a given value passed by rvalue reference.
685
     *
686
     * \param value Value to store in the holder.
687
     */
688
    inplace_optional_holder(T&& value)
689
36
    {
690
36
        new (m_storage.getStorage()) T(std::move(value));
691
36
        m_hasValue = true;
692
36
    }
693
694
    // called from allocatorPropagatingCopy
695
    /**
696
     * Constructor from a given value passed by rvalue reference which prevents initialization.
697
     *
698
     * \param value Value to store in the holder.
699
     */
700
    template <typename U = T,
701
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
702
    inplace_optional_holder(NoInitT, T&& value)
703
6
    {
704
6
        new (m_storage.getStorage()) T(NoInit, std::move(value));
705
6
        m_hasValue = true;
706
6
    }
707
708
    /**
709
     * Copy constructor.
710
     *
711
     * \param other Other holder to copy.
712
     */
713
    inplace_optional_holder(const inplace_optional_holder& other)
714
72
    {
715
72
        if (other.hasValue())
716
44
        {
717
44
            new (m_storage.getStorage()) T(*other.m_storage.getObject());
718
44
            m_hasValue = true;
719
44
        }
720
72
    }
721
722
    /**
723
     * Copy constructor which prevents initialization.
724
     *
725
     * \param other Other holder to copy.
726
     */
727
    template <typename U = T,
728
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
729
    inplace_optional_holder(NoInitT, const inplace_optional_holder& other)
730
2
    {
731
2
        if (other.hasValue())
732
1
        {
733
1
            new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
734
1
            m_hasValue = true;
735
1
        }
736
2
    }
737
738
    /**
739
     * Move constructor.
740
     *
741
     * \param other Other holder to move.
742
     */
743
    inplace_optional_holder(inplace_optional_holder&& other) noexcept(
744
            std::is_nothrow_move_constructible<in_place_storage<T>>::value)
745
4
    {
746
4
        if (other.hasValue())
747
2
        {
748
2
            m_storage = std::move(other.m_storage);
749
2
            other.m_hasValue = false;
750
2
            m_hasValue = true;
751
2
        }
752
4
    }
753
754
    /**
755
     * Move constructor which prevents initialization.
756
     *
757
     * \param other Other holder to move.
758
     */
759
    template <typename U = T,
760
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
761
    inplace_optional_holder(NoInitT, inplace_optional_holder&& other) noexcept(
762
            std::is_nothrow_move_constructible<in_place_storage<T>>::value)
763
4
    {
764
4
        if (other.hasValue())
765
2
        {
766
2
            m_storage.assign(NoInit, std::move(other.m_storage));
767
2
            other.m_hasValue = false;
768
2
            m_hasValue = true;
769
2
        }
770
4
    }
771
772
    /**
773
     * Constructor that initializes the value inplace by forwarding the arguments to the object's constructor.
774
     *
775
     * \param parameters Parameters for object's constructor.
776
     */
777
    template <typename... U>
778
    explicit inplace_optional_holder(InPlaceT, U&&... parameters)
779
3
    {
780
3
        new (m_storage.getStorage()) T(std::forward<U>(parameters)...);
781
3
        m_hasValue = true;
782
3
    }
783
784
    /**
785
     * Destructor.
786
     */
787
    ~inplace_optional_holder()
788
66.9k
    {
789
66.9k
        reset();
790
66.9k
    }
791
792
    /**
793
     * Assignment operator.
794
     *
795
     * \param other Other holder to copy-assign.
796
     *
797
     * \return Reference to the current holder.
798
     */
799
    inplace_optional_holder& operator=(const inplace_optional_holder& other)
800
61
    {
801
61
        if (this != &other)
802
60
        {
803
60
            reset();
804
60
            if (other.hasValue())
805
59
            {
806
59
                new (m_storage.getStorage()) T(*other.m_storage.getObject());
807
59
                m_hasValue = true;
808
59
            }
809
60
        }
810
811
61
        return *this;
812
61
    }
813
814
    /**
815
     * Assignment operator which prevents initialization.
816
     *
817
     * \param other Other holder to copy-assign.
818
     *
819
     * \return Reference to the current holder.
820
     */
821
    template <typename U = T,
822
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
823
    inplace_optional_holder& assign(NoInitT, const inplace_optional_holder& other)
824
9
    {
825
9
        if (this != &other)
826
8
        {
827
8
            reset();
828
8
            if (other.hasValue())
829
7
            {
830
7
                new (m_storage.getStorage()) T(NoInit, *other.m_storage.getObject());
831
7
                m_hasValue = true;
832
7
            }
833
8
        }
834
835
9
        return *this;
836
9
    }
837
838
    /**
839
     * Move assignment operator.
840
     *
841
     * \param other Other holder to move-assign.
842
     *
843
     * \return Reference to the current holder.
844
     */
845
    inplace_optional_holder& operator=(inplace_optional_holder&& other)
846
16.3k
    {
847
16.3k
        if (this != &other)
848
16.3k
        {
849
16.3k
            reset();
850
16.3k
            if (other.hasValue())
851
16.3k
            {
852
16.3k
                m_storage = std::move(other.m_storage);
853
16.3k
                other.m_hasValue = false;
854
16.3k
                m_hasValue = true;
855
16.3k
            }
856
16.3k
        }
857
858
16.3k
        return *this;
859
16.3k
    }
860
861
    /**
862
     * Move assignment operator which prevents initialization.
863
     *
864
     * \param other Other holder to move-assign.
865
     *
866
     * \return Reference to the current holder.
867
     */
868
    template <typename U = T,
869
            typename std::enable_if<std::is_constructible<U, NoInitT, U>::value, int>::type = 0>
870
    inplace_optional_holder& assign(NoInitT, inplace_optional_holder&& other)
871
10
    {
872
10
        if (this != &other)
873
9
        {
874
9
            reset();
875
9
            if (other.hasValue())
876
8
            {
877
8
                m_storage.assign(NoInit, std::move(other.m_storage));
878
8
                other.m_hasValue = false;
879
8
                m_hasValue = true;
880
8
            }
881
9
        }
882
883
10
        return *this;
884
10
    }
885
886
    /**
887
     * Assignment operator from value.
888
     *
889
     * \param value Value to assign.
890
     *
891
     * \return Reference to the current holder.
892
     */
893
    inplace_optional_holder& operator=(const T& value)
894
33.4k
    {
895
33.4k
        set(value);
896
897
33.4k
        return *this;
898
33.4k
    }
899
900
    /**
901
     * Assignment operator from rvalue reference to value.
902
     *
903
     * \param value Value to move-assign.
904
     *
905
     * \return Reference to the current holder.
906
     */
907
    inplace_optional_holder& operator=(T&& value)
908
16.8k
    {
909
16.8k
        set(std::move(value));
910
911
16.8k
        return *this;
912
16.8k
    }
913
914
    /**
915
     * Resets the current holder (switch to the unset state).
916
     */
917
    void reset() noexcept
918
133k
    {
919
133k
        if (hasValue())
920
50.5k
        {
921
50.5k
            m_storage.getObject()->~T();
922
50.5k
            m_hasValue = false;
923
50.5k
        }
924
133k
    }
925
926
    /**
927
     * Gets whether the holder has any value.
928
     *
929
     * \return True when this holder has assigned any value. False otherwise.
930
     */
931
    bool hasValue() const noexcept
932
201k
    {
933
201k
        return m_hasValue;
934
201k
    }
935
936
    /**
937
     * Dereference operator *.
938
     *
939
     * \return Const reference to the assigned value.
940
     *
941
     * \throw CppRuntimeException when the holder is unset.
942
     */
943
    const T& operator*() const
944
48.8k
    {
945
48.8k
        this->checkHasValue();
946
48.8k
        return *m_storage.getObject();
947
48.8k
    }
948
949
    /**
950
     * Dereference operator *.
951
     *
952
     * \return Reference to the assigned value.
953
     *
954
     * \throw CppRuntimeException when the holder is unset.
955
     */
956
    T& operator*()
957
1.35k
    {
958
1.35k
        this->checkHasValue();
959
1.35k
        return *m_storage.getObject();
960
1.35k
    }
961
962
private:
963
    template <typename U = T>
964
    void set(U&& value)
965
50.3k
    {
966
50.3k
        reset();
967
50.3k
        new (m_storage.getStorage()) T(std::forward<U>(value));
968
50.3k
        m_hasValue = true;
969
50.3k
    }
970
971
    in_place_storage<T> m_storage;
972
    bool m_hasValue = false;
973
};
974
975
} // namespace detail
976
977
// Be aware that if Inplace/HeapOptionalHolder is defined by typename, C++ compiler will have problem with
978
// template function overload, see HashCodeUtil.h (overloads for objects and for Inplace/HeapOptionalHolder).
979
/**
980
 * Optional holder which uses in place storage.
981
 */
982
template <typename T>
983
using InplaceOptionalHolder = detail::inplace_optional_holder<T>;
984
985
/**
986
 * Optional holder which uses heap storage.
987
 */
988
template <typename T, typename ALLOC = std::allocator<T>>
989
using HeapOptionalHolder = detail::heap_optional_holder<T, ALLOC>;
990
991
} // namespace zserio
992
993
#endif // ifndef ZSERIO_OPTIONAL_HOLDER_H_INC