Coverage Report

Created: 2026-08-13 06:48

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/stdplus/include/stdplus/handle/managed.hpp
Line
Count
Source
1
#pragma once
2
#include <cstdlib>
3
#include <optional>
4
#include <tuple>
5
#include <type_traits>
6
#include <utility>
7
8
namespace stdplus
9
{
10
11
/** @brief   An RAII handle which takes an object and calls a user specified
12
 *           function on the object when it should be cleaned up.
13
 *  @details This is useful for adding RAII semantics to non-RAII things like
14
 *           file descriptors, or c structs allocated with special routines.
15
 *           We could make a simple file descriptor wrapper that is
16
 *           automatically closed:
17
 *
18
 *           struct CloseFd {
19
 *               void operator()(int&& fd) { close(fd); }
20
 *           };
21
 *           using Fd = Managed<int>::HandleF<closefd>;
22
 *
23
 *           void some_func()
24
 *           {
25
 *               Fd fd(open("somefile", 0));
26
 *               char buf[4096];
27
 *               int amt = read(*fd, buf, sizeof(buf));
28
 *               printf("%.*s\n", amt, data);
29
 *           }
30
 */
31
template <typename T, typename... As>
32
struct Managed
33
{
34
    template <typename Drop>
35
    class HandleF
36
    {
37
      protected:
38
        static inline constexpr bool drop_noexcept =
39
            noexcept(Drop()(std::declval<T>(), std::declval<As&>()...));
40
41
      public:
42
        /** @brief Creates a handle owning the object
43
         *
44
         *  @param[in] maybeV - Maybe the object being managed
45
         */
46
        template <typename... Vs>
47
        constexpr explicit HandleF(
48
            std::optional<T>&& maybeV,
49
            Vs&&... vs) noexcept(std::
50
                                     is_nothrow_move_constructible_v<
51
                                         std::optional<T>> &&
52
                                 noexcept(std::tuple<As...>(
53
                                     std::declval<Vs>()...))) :
54
            as(std::forward<Vs>(vs)...), maybeT(std::move(maybeV))
55
        {}
56
        template <typename... Vs>
57
        constexpr explicit HandleF(T&& maybeV, Vs&&... vs) noexcept(
58
            std::is_nothrow_move_constructible_v<std::optional<T>> &&
59
            noexcept(std::tuple<As...>(std::declval<Vs>()...))) :
60
5.47k
            as(std::forward<Vs>(vs)...), maybeT(std::move(maybeV))
61
5.47k
        {}
62
63
        HandleF(const HandleF& other) = delete;
64
        HandleF& operator=(const HandleF& other) = delete;
65
66
        constexpr HandleF(HandleF&& other) noexcept(
67
            std::is_nothrow_move_constructible_v<std::tuple<As...>> &&
68
            std::is_nothrow_move_constructible_v<std::optional<T>>) :
69
            as(std::move(other.as)), maybeT(std::move(other.maybeT))
70
        {
71
            other.maybeT = std::nullopt;
72
        }
73
74
        constexpr HandleF& operator=(HandleF&& other) noexcept(
75
            std::is_nothrow_move_assignable_v<std::tuple<As...>> &&
76
            noexcept(std::declval<HandleF>().reset(
77
                std::declval<std::optional<T>>())))
78
        {
79
            if (this != &other)
80
            {
81
                reset(std::move(other.maybeT));
82
                as = std::move(other.as);
83
                other.maybeT = std::nullopt;
84
            }
85
            return *this;
86
        }
87
88
        virtual ~HandleF() noexcept(
89
            std::is_nothrow_destructible_v<std::tuple<As...>> &&
90
            std::is_nothrow_destructible_v<std::optional<T>> &&
91
            noexcept(std::declval<HandleF>().reset()))
92
5.47k
        {
93
5.47k
            reset();
94
5.47k
        }
95
96
        /** @brief Gets the managed object
97
         *
98
         *  @return A pointer to the object
99
         */
100
        constexpr const T* operator->() const noexcept
101
        {
102
            return &(*maybeT);
103
        }
104
105
        /** @brief Gets the managed object
106
         *
107
         *  @return A reference to the object
108
         */
109
        constexpr const T& operator*() const& noexcept
110
40.2k
        {
111
40.2k
            return *maybeT;
112
40.2k
        }
113
114
        /** @brief Determine if we are managing an object
115
         *
116
         *  @return Do we currently have an object
117
         */
118
        constexpr explicit operator bool() const noexcept
119
        {
120
            return static_cast<bool>(maybeT);
121
        }
122
123
        /** @brief Determine if we are managing an object
124
         *
125
         *  @return Do we currently have an object
126
         */
127
        constexpr bool has_value() const noexcept
128
        {
129
            return maybeT.has_value();
130
        }
131
132
        /** @brief Gets the managed object
133
         *
134
         *  @throws std::bad_optional_access if it has no object
135
         *  @return A reference to the managed object
136
         */
137
        constexpr const T& value() const&
138
        {
139
            return maybeT.value();
140
        }
141
142
        /** @brief Gets the managed object if it exists
143
         *
144
         *  @throws std::bad_optional_access if it has no object
145
         *  @return A reference to the managed object
146
         */
147
        constexpr const std::optional<T>& maybe_value() const& noexcept
148
        {
149
            return maybeT;
150
        }
151
152
        /** @brief Resets the managed value to a new value
153
         *         The container takes ownership of the value
154
         *
155
         *  @param[in] maybeV - Maybe the new value
156
         */
157
        constexpr void reset(std::optional<T>&& maybeV) noexcept(
158
            drop_noexcept &&
159
            std::is_nothrow_move_assignable_v<std::optional<T>>)
160
5.47k
        {
161
5.47k
            maybeDrop(std::index_sequence_for<As...>());
162
5.47k
            maybeT = std::move(maybeV);
163
5.47k
        }
164
        constexpr void reset(T&& maybeV) noexcept(
165
            drop_noexcept &&
166
            std::is_nothrow_move_assignable_v<std::optional<T>>)
167
        {
168
            maybeDrop(std::index_sequence_for<As...>());
169
            maybeT = std::move(maybeV);
170
        }
171
172
        /** @brief A shorthand reset function for convenience
173
         *         Same as calling reset(std::nullopt)
174
         */
175
        constexpr void reset() noexcept(drop_noexcept)
176
5.47k
        {
177
5.47k
            reset(std::nullopt);
178
5.47k
        }
179
180
        /** @brief Releases the managed value and transfers ownership
181
         *         to the caller.
182
         *
183
         *  @throws std::bad_optional_access if it has no object
184
         *  @return The value that was managed
185
         */
186
        [[nodiscard]] constexpr T release()
187
0
        {
188
0
            T ret = std::move(maybeT.value());
189
0
            maybeT = std::nullopt;
190
0
            return ret;
191
0
        }
192
193
        /** @brief Releases the managed value and transfers ownership
194
         *         to the caller.
195
         *
196
         *  @return Maybe the value that was managed
197
         */
198
        [[nodiscard]] constexpr std::optional<T> maybe_release() noexcept
199
        {
200
            std::optional<T> ret = std::move(maybeT);
201
            maybeT = std::nullopt;
202
            return ret;
203
        }
204
205
        /** @brief Reference the contained data
206
         *
207
         *  @return A reference to the contained data
208
         */
209
        constexpr const std::tuple<As...>& data() const noexcept
210
        {
211
            return as;
212
        }
213
        constexpr std::tuple<As...>& data() noexcept
214
        {
215
            return as;
216
        }
217
218
      protected:
219
        /* Hold the data parameterized for this container */
220
        std::tuple<As...> as;
221
222
      private:
223
        /* Stores the managed object if we have one */
224
        std::optional<T> maybeT;
225
226
        template <size_t... Indices>
227
        void maybeDrop(std::index_sequence<Indices...>) noexcept(drop_noexcept)
228
5.47k
        {
229
5.47k
            if (maybeT)
230
5.47k
            {
231
5.47k
                Drop()(std::move(*maybeT), std::get<Indices>(as)...);
232
5.47k
            }
233
5.47k
        }
234
    };
235
236
    template <void (*drop)(T&&, As&...)>
237
    struct Dropper
238
    {
239
        void operator()(T&& t, As&... as) noexcept(noexcept(drop))
240
5.47k
        {
241
5.47k
            drop(std::move(t), as...);
242
5.47k
        }
243
    };
244
245
    template <void (*drop)(T&&, As&...)>
246
    using Handle = HandleF<Dropper<drop>>;
247
};
248
249
} // namespace stdplus