Coverage Report

Created: 2024-07-18 11:41

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