summaryrefslogtreecommitdiff
path: root/asl/option.hpp
blob: ddd531d8868a390f24f2c7705d5a678febe2b34d (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
#pragma once

#include "asl/assert.hpp"
#include "asl/meta.hpp"
#include "asl/maybe_uninit.hpp"

namespace asl
{

struct nullopt_t {};
static constexpr nullopt_t nullopt{};

// @Todo(option) Niche
// @Todo(option) Reference
// @Todo(option) Function
// @Todo(option) Arrays

template<is_object T>
class option
{
    maybe_uninit<T> m_payload;
    bool            m_has_value = false;

    template<typename... Args>
    constexpr void construct(Args&&... args) &
    {
        ASL_ASSERT(!m_has_value);
        m_payload.init_unsafe(ASL_FWD(args)...);
        m_has_value = true;
    }

    template<typename Arg>
    constexpr void assign(Arg&& arg) &
    {
        ASL_ASSERT(m_has_value);
        m_payload.as_init_unsafe() = ASL_FWD(arg);
    }

public:
    constexpr option() = default;
    constexpr option(nullopt_t) {} // NOLINT(*-explicit-conversions)

    template<typename U>
    // NOLINTNEXTLINE(*-explicit-conversions)
    constexpr option(U&& value) requires constructible<T, U>
    {
        construct(ASL_FWD(value));
    }

    constexpr option(const option& other) requires copy_constructible<T>
    {
        if (other.m_has_value)
        {
            construct(other.m_payload.as_init_unsafe());
        }
    }

    constexpr option(option&& other) requires move_constructible<T>
    {
        if (other.m_has_value)
        {
            construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
        }
    }

    constexpr option& operator=(const option& other) & requires copy_assignable<T> && copy_constructible<T>
    {
        if (&other == this) { return *this; }

        if (other.m_has_value)
        {
            if (m_has_value)
            {
                assign(other.m_payload.as_init_unsafe());
            }
            else
            {
                construct(other.m_payload.as_init_unsafe());
            }
        }
        else if (m_has_value)
        {
            reset();
        }
        
        return *this;
    }

    constexpr option& operator=(option&& other) & requires move_assignable<T> && move_constructible<T>
    {
        if (&other == this) { return *this; }

        if (other.m_has_value)
        {
            if (m_has_value)
            {
                assign(ASL_MOVE(other.m_payload.as_init_unsafe()));
            }
            else
            {
                construct(ASL_MOVE(other.m_payload.as_init_unsafe()));
            }
        }
        else if (m_has_value)
        {
            reset();
        }
        
        return *this;
    }
    
    constexpr ~option() = default;
    constexpr ~option() requires (!trivially_destructible<T>)
    {
        reset();
    }

    constexpr void reset()
    {
        if (m_has_value)
        {
            m_payload.uninit_unsafe();
            m_has_value = false;
        }
    }

    constexpr bool has_value() const { return m_has_value; }

    // @Todo Do we want this on rvalues? Or maybe some kind of unwrap?
    constexpr T&& value() &&
    {
        ASL_ASSERT(m_has_value); // @Todo Release assert
        return ASL_MOVE(m_payload).as_init_unsafe();
    }

    constexpr T& value() &
    {
        ASL_ASSERT(m_has_value); // @Todo Release assert
        return m_payload.as_init_unsafe();
    }

    constexpr const T& value() const&
    {
        ASL_ASSERT(m_has_value); // @Todo Release assert
        return m_payload.as_init_unsafe();
    }
};

template<typename T>
option(T) -> option<T>;

} // namespace asl