xref: /openbmc/sdbusplus/include/sdbusplus/async/stdexec/__detail/__optional.hpp (revision 10d0b4b7d1498cfd5c3d37edea271a54d1984e41)
1 /*
2  * Copyright (c) 2021-2024 NVIDIA Corporation
3  *
4  * Licensed under the Apache License Version 2.0 with LLVM Exceptions
5  * (the "License"); you may not use this file except in compliance with
6  * the License. You may obtain a copy of the License at
7  *
8  *   https://llvm.org/LICENSE.txt
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 #pragma once
17 
18 #include "__execution_fwd.hpp"
19 
20 // include these after __execution_fwd.hpp
21 #include "__concepts.hpp"
22 #include "__scope.hpp"
23 
24 #include <new> // IWYU pragma: keep for ::new
25 #include <exception>
26 #include <memory>
27 #include <utility>
28 
29 namespace stdexec {
30   namespace __opt {
31     struct __bad_optional_access : std::exception {
32       [[nodiscard]]
whatstdexec::__opt::__bad_optional_access33       auto what() const noexcept -> const char* override {
34         return "stdexec::__optional: bad access";
35       }
36     };
37 
__mk_has_value_guard(bool & __has_value)38     inline auto __mk_has_value_guard(bool& __has_value) noexcept {
39       __has_value = true;
40       return __scope_guard{[&]() noexcept { __has_value = false; }};
41     }
42 
43     inline constexpr struct __nullopt_t {
44     } __nullopt{};
45 
46     // A simplified version of std::optional for better compile times
47     template <class _Tp>
48     struct __optional {
49       static_assert(destructible<_Tp>);
50 
51       union {
52         _Tp __value_;
53       };
54 
55       bool __has_value_ = false;
56 
__optionalstdexec::__opt::__optional57       __optional() noexcept {
58       }
59 
__optionalstdexec::__opt::__optional60       __optional(__nullopt_t) noexcept {
61       }
62 
63       __optional(__optional&&) = delete; // immovable for simplicity's sake
64 
65       template <__not_decays_to<__optional> _Up>
66         requires constructible_from<_Tp, _Up>
__optionalstdexec::__opt::__optional67       __optional(_Up&& __v) noexcept(__nothrow_constructible_from<_Tp, _Up>) {
68         emplace(static_cast<_Up&&>(__v));
69       }
70 
71       template <class... _Us>
72         requires constructible_from<_Tp, _Us...>
__optionalstdexec::__opt::__optional73       __optional(std::in_place_t, _Us&&... __us)
74         noexcept(__nothrow_constructible_from<_Tp, _Us...>) {
75         emplace(static_cast<_Us&&>(__us)...);
76       }
77 
~__optionalstdexec::__opt::__optional78       ~__optional() {
79         if (__has_value_) {
80           std::destroy_at(std::addressof(__value_));
81         }
82       }
83 
84       // The following emplace function must take great care to avoid use-after-free bugs.
85       // If the object being constructed calls `start` on a newly created operation state
86       // (as does the object returned from `submit`), and if `start` completes inline, it
87       // could cause the destruction of the outer operation state that owns *this. The
88       // function below uses the following pattern to avoid this:
89       // 1. Set __has_value_ to true.
90       // 2. Create a scope guard that will reset __has_value_ to false if the constructor
91       //    throws.
92       // 3. Construct the new object in the storage, which may cause the invalidation of
93       //    *this. The emplace function must not access any members of *this after this point.
94       // 4. Dismiss the scope guard, which will leave __has_value_ set to true.
95       // 5. Return a reference to the new object -- which may be invalid! Calling code
96       //    must be aware of the danger.
97       template <class... _Us>
98         requires constructible_from<_Tp, _Us...>
emplacestdexec::__opt::__optional99       auto emplace(_Us&&... __us) noexcept(__nothrow_constructible_from<_Tp, _Us...>) -> _Tp& {
100         reset();
101         auto __sg = __mk_has_value_guard(__has_value_);
102         auto* __p = ::new (static_cast<void*>(std::addressof(__value_)))
103           _Tp{static_cast<_Us&&>(__us)...};
104         __sg.__dismiss();
105         return *std::launder(__p);
106       }
107 
108       template <class _Fn, class... _Args>
109         requires same_as<_Tp, __call_result_t<_Fn, _Args...>>
__emplace_fromstdexec::__opt::__optional110       auto __emplace_from(_Fn&& __f, _Args&&... __args) noexcept(__nothrow_callable<_Fn, _Args...>)
111         -> _Tp& {
112         reset();
113         auto __sg = __mk_has_value_guard(__has_value_);
114         auto* __p = ::new (static_cast<void*>(std::addressof(__value_)))
115           _Tp(static_cast<_Fn&&>(__f)(static_cast<_Args&&>(__args)...));
116         __sg.__dismiss();
117         return *std::launder(__p);
118       }
119 
valuestdexec::__opt::__optional120       auto value() & -> _Tp& {
121         if (!__has_value_) {
122           STDEXEC_THROW(__bad_optional_access());
123         }
124         return __value_;
125       }
126 
valuestdexec::__opt::__optional127       auto value() const & -> const _Tp& {
128         if (!__has_value_) {
129           STDEXEC_THROW(__bad_optional_access());
130         }
131         return __value_;
132       }
133 
valuestdexec::__opt::__optional134       auto value() && -> _Tp&& {
135         if (!__has_value_) {
136           STDEXEC_THROW(__bad_optional_access());
137         }
138         return static_cast<_Tp&&>(__value_);
139       }
140 
operator *stdexec::__opt::__optional141       auto operator*() & noexcept -> _Tp& {
142         STDEXEC_ASSERT(__has_value_);
143         return __value_;
144       }
145 
operator *stdexec::__opt::__optional146       auto operator*() const & noexcept -> const _Tp& {
147         STDEXEC_ASSERT(__has_value_);
148         return __value_;
149       }
150 
operator *stdexec::__opt::__optional151       auto operator*() && noexcept -> _Tp&& {
152         STDEXEC_ASSERT(__has_value_);
153         return static_cast<_Tp&&>(__value_);
154       }
155 
operator ->stdexec::__opt::__optional156       auto operator->() & noexcept -> _Tp* {
157         STDEXEC_ASSERT(__has_value_);
158         return &__value_;
159       }
160 
operator ->stdexec::__opt::__optional161       auto operator->() const & noexcept -> const _Tp* {
162         STDEXEC_ASSERT(__has_value_);
163         return &__value_;
164       }
165 
166       [[nodiscard]]
has_valuestdexec::__opt::__optional167       auto has_value() const noexcept -> bool {
168         return __has_value_;
169       }
170 
resetstdexec::__opt::__optional171       void reset() noexcept {
172         if (__has_value_) {
173           std::destroy_at(std::addressof(__value_));
174           __has_value_ = false;
175         }
176       }
177     };
178   } // namespace __opt
179 
180   using __opt::__optional;
181   using __opt::__bad_optional_access;
182   using __opt::__nullopt;
183 } // namespace stdexec
184