Coverage Report

Created: 2023-12-13 14:58

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