Stroika Library 3.0d16
 
Loading...
Searching...
No Matches
Optional.h
Go to the documentation of this file.
1/*
2 * Copyright(c) Sophist Solutions, Inc. 1990-2025. All rights reserved
3 */
4#ifndef _Stroika_Foundation_Memory_Optional_h_
5#define _Stroika_Foundation_Memory_Optional_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <mutex>
10#include <optional>
11#include <shared_mutex>
12
13#include "Stroika/Foundation/Common/Common.h"
15#include "Stroika/Foundation/Common/Concepts.h"
17#include "Stroika/Foundation/Containers/Adapters/Adder.h"
19#include "Stroika/Foundation/Execution/NullMutex.h"
21#include "Stroika/Foundation/Memory/Common.h"
22
23/**
24 * \file
25 *
26 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
27 *
28 * TODO:
29 *
30 * NOTE TO SUGGEST TO C++ standards -
31 * Things I miss most about my Optional versus std::optional
32 * > Value () - what they call value_or - should take T{} as default argument. About 25% of teh time
33 * that's what I want, and its much more clear/terse.
34 *
35 * > Accumulate method, and operator +=, operator-= etc overloads calling Accumulate(). Much simpler
36 * and more elegant code with those methods.
37 */
38
39namespace Stroika::Foundation::Memory {
40
41 /**
42 * \brief AccumulateIf () add in the rhs argument value to lhs optional, but if both were missing leave 'lhs'
43 * as still missing, and if only RHS available, assign it to the left.
44 *
45 * \par Example Usage
46 * \code
47 * optional<int> accumulator;
48 * optional<int> SomeFunctionToGetOptionalValue();
49 * if (accumulator or (tmp = SomeFunctionToGetOptionalValue())) {
50 * accumulator = accumulator.Value () + tmp;
51 * }
52 * \endcode
53 * VERSUS
54 * \code
55 * AccumulateIf (&accumulator, SomeFunctionToGetOptionalValue ());
56 * \endcode
57 *
58 * \par Example Usage
59 * \code
60 * optional<Sequence<InternetAddress>>> addresses;
61 * Memory::AccumulateIf (&addresses, IO::Network::InternetAddress{connection.GET ().GetDataTextInputStream ().ReadAll ().Trim ()});
62 * \endcode
63 *
64 * Notes:
65 * \pre lhsOptionalValue != nullptr (for optional* first argument)
66 * \note Overloads that take optional* first argument accumulate in place and return nothing, while
67 * overloads taking optional<T> as the first augment return the computed result.
68 * overloads taking optional<CONTAINER> or optional<CONTAINER>* dont take an op as argument, but assume the operation is 'Add' to the container
69 *
70 * \note ITS CONFUSING direction of if-test for this versus CopyToIf
71 *
72 * \note
73 * typical OP arguments would be:
74 * std::plus{} **the default**
75 * std::minus{}
76 * std::multiplies{}
77 * std::divides{}
78 */
79 template <typename T, convertible_to<T> CONVERTIBLE_TO_T, convertible_to<function<T (T, T)>> OP = plus<T>>
80 void AccumulateIf (optional<T>* lhsOptionalValue, const optional<CONVERTIBLE_TO_T>& rhsOptionalValue, const OP& op = OP{});
81 template <typename T, convertible_to<function<T (T, T)>> OP = plus<T>>
82 void AccumulateIf (optional<T>* lhsOptionalValue, const T& rhsValue, const OP& op = OP{});
83 template <typename T, template <typename> typename CONTAINER>
84 requires (is_convertible_v<typename Containers::Adapters::Adder<CONTAINER<T>>::value_type, T>)
85 void AccumulateIf (optional<CONTAINER<T>>* lhsOptionalValue, const optional<T>& rhsOptionalValue);
86 template <typename T, template <typename> typename CONTAINER>
87 requires (is_convertible_v<typename Containers::Adapters::Adder<CONTAINER<T>>::value_type, T>)
88 void AccumulateIf (optional<CONTAINER<T>>* lhsOptionalValue, const T& rhsValue);
89 template <typename T, convertible_to<T> CONVERTIBLE_TO_T, convertible_to<function<T (T, T)>> OP = plus<T>>
90 optional<T> AccumulateIf (const optional<T>& lhsOptionalValue, const optional<CONVERTIBLE_TO_T>& rhsOptionalValue, const OP& op = OP{});
91 template <typename T, convertible_to<function<T (T, T)>> OP = plus<T>>
92 optional<T> AccumulateIf (const optional<T>& lhsOptionalValue, const T& rhsValue, const OP& op = OP{});
93 template <typename T, template <typename> typename CONTAINER>
94 requires (is_convertible_v<typename Containers::Adapters::Adder<CONTAINER<T>>::value_type, T>)
95 optional<CONTAINER<T>> AccumulateIf (const optional<CONTAINER<T>>& lhsOptionalValue, const optional<T>& rhsOptionalValue);
96 template <typename T, template <typename> typename CONTAINER>
97 optional<CONTAINER<T>> AccumulateIf (const optional<CONTAINER<T>>& lhsOptionalValue, const T& rhsValue);
98
99 /**
100 * Assign (overwriting) the value held by this optional (first argument) if one is present with destination (second) argument if engaged. Assigns from right to left.
101 *
102 * The point of this to to facilitate a common idiom, where you want to maintain an existing value unless you
103 * get an update. This function is ANALAGOUS to
104 * if (o.has_value()) {
105 * destArgVal = *o;
106 * }
107 *
108 * but can be done in a single line.
109 *
110 * \par Example Usage
111 * \code
112 * int curValue = 3;
113 * Memory::CopyToIf (&curValue, someMap.Lookup (KEY_VALUE)); // curValue will be 3, or overwritten by whatever value MAY have been in someMap
114 * \endcode
115 *
116 * \par Example Usage
117 * \code
118 * optional<int> curValue = getSomeValue ();
119 * optional<long> oVal = someMap.Lookup (KEY_VALUE);
120 * Memory::CopyToIf (&curValue, oVal); // curValue retains its value from before CopyToIf if oVal was missing
121 * \endcode
122 *
123 * @see Value
124 *
125 * @todo CONSIDER overload where first arg is not ptr (by value) and result is written to return value
126 */
127 template <typename T, typename CONVERTABLE_TO_TYPE>
128 void CopyToIf (CONVERTABLE_TO_TYPE* to, const optional<T>& copyFromIfHasValue);
129 template <typename T, typename CONVERTABLE_TO_OPTIONAL_OF_TYPE>
130 void CopyToIf (optional<CONVERTABLE_TO_OPTIONAL_OF_TYPE>* to, const optional<T>& copyFromIfHasValue);
131
132 namespace Private_ {
133 template <typename T>
134 concept INullCoalescable = requires (T t) {
135 static_cast<bool> (t);
136 *t;
137 };
138 template <typename OT>
139 using OptionalType2ValueType = remove_cvref_t<decltype (*declval<OT> ())>;
140 }
141
142 /**
143 * \brief return one of l, or r, with first preference for which is engaged, and second preference for left-to-right.
144 *
145 * So Equivalent to (depending on overload)
146 * static_cast<bool> (l)? l : r;
147 * or
148 * static_cast<bool> (l)? *l : r;
149 *
150 * This is similar to/inspired by C# ?? operator (https://docs.microsoft.com/en-us/dotnet/csharp/language-reference/operators/null-coalescing-operator)
151 *
152 * \note This is handy because there is no default argument for std::optional<>::value_or () - there should be (like this).
153 * \note a bit like value_or, but RHS arg can be optional or T, and depending returns optional or T and this takes default value
154 * @see Value ()
155 *
156 * \par Example Usage
157 * \code
158 * optional<uint64_t> workingOrResidentSetSize = NullCoalesce (thisProcess.fWorkingSetSize, thisProcess.fResidentMemorySize);
159 * uint64_t useMemUsageSz = NullCoalesce (workingOrResidentSetSize, 1024);
160 * uint64_t useMemUsageSz2 = NullCoalesce (workingOrResidentSetSize);
161 * \endcode
162 *
163 * \note NullCoalesce overloads returns a const T& internal pointer: that means the caller
164 * MAY need to be careful to finish using the result of the function before the end of the full expression calling NullCoalesce ().
165 */
166 template <Private_::INullCoalescable OT>
167 const OT& NullCoalesce (const OT& l, const OT& r);
168 template <Private_::INullCoalescable OT, convertible_to<const Private_::OptionalType2ValueType<OT>&> DEFAULT_TYPE = Private_::OptionalType2ValueType<OT>>
169 const Private_::OptionalType2ValueType<OT>& NullCoalesce (const OT& l, const DEFAULT_TYPE& r = DEFAULT_TYPE{});
170
171 /**
172 * \brief Same as *t, but Requires that 't' is engaged.
173 * \note operator* for optional returns a const T& internal pointer, and so does this. That means the caller
174 * MAY need to be careful to finish using the result of the function before the end of the full expression calling ValueOf ().
175 * But again, this is the same as if they used *v, which is the obvious alternative.
176 */
177 template <typename T>
178 constexpr const T& ValueOf (const optional<T>& t);
179
180 /**
181 */
182 template <typename T, typename EXCEPT = bad_optional_access>
183 const T& ValueOfOrThrow (const optional<T>& t, const EXCEPT& throwIfNull = {});
184
185 /**
186 * \brief if you can copy an IN_T to an OUT_T, you should be able to copy an optional<IN_T> to an optional<OUT_T>
187 *
188 * Actually, sometimes you can. But due to C++ one-step conversion operator rule, some cases where you would want this to work it doesn't.
189 * More likely though, you need to specify some other lambda todo the conversion of T like below:
190 *
191 * \par Example Usage
192 * \code
193 * optional<URI> ShowAsExternalURL;
194 * optional<String> s = OptionallyCopy<String>(ShowAsExternalURL,[](URI u) {return u.As<String>();})
195 * \endcode
196 */
197 template <typename OUT_T, Common::explicitly_convertible_to<OUT_T> IN_T>
198 optional<OUT_T> OptionallyCopy (const optional<IN_T>& in);
199 template <typename OUT_T, typename IN_T, invocable<IN_T> IN_TO_OUT_CONVERTER>
200 optional<OUT_T> OptionallyCopy (const optional<IN_T>& in, IN_TO_OUT_CONVERTER&& cvt)
201 requires (convertible_to<invoke_result_t<IN_TO_OUT_CONVERTER, IN_T>, optional<OUT_T>>);
202
203 /**
204 * wrappers on std c++23 monadic optional support, til we can assume c++23
205 */
206 template <typename T, class F>
207 constexpr auto And_Then (const optional<T>& o, F&& f)
208 {
209#if __cplusplus > 202302L || _HAS_CXX23 || (_LIBCPP_STD_VER >= 23)
210 return o.and_then (forward<F> (f));
211#else
212 if (o.has_value ()) {
213 return std::invoke (std::forward<F> (f), *o);
214 }
215 else {
216 return std::remove_cvref_t<std::invoke_result_t<F, T>>{};
217 }
218#endif
219 }
220 /**
221 * wrappers on std c++23 monadic optional support, til we can assume c++23
222 */
223 template <typename T, class F>
224 constexpr auto Or_Else (const optional<T>& o, F&& f)
225 {
226#if __cplusplus > 202302L || _HAS_CXX23 || (_LIBCPP_STD_VER >= 23)
227 return o.or_else (forward<F> (f));
228#else
229 if (o.has_value ()) {
230 return o;
231 }
232 else {
233 return forward<F> (f) ();
234 }
235#endif
236 }
237 /**
238 * wrappers on std c++23 monadic optional support, til we can assume c++23
239 */
240 template <typename T, class F>
241 constexpr auto Transform (const optional<T>& o, F&& f)
242 {
243#if __cplusplus > 202302L || _HAS_CXX23 || (_LIBCPP_STD_VER >= 23)
244 return o.transform (forward<F> (f));
245#else
246 using U = std::remove_cv_t<std::invoke_result_t<F, T>>;
247 if (o.has_value ()) {
248 return optional<U>{forward<F> (f) (*o)};
249 }
250 else {
251 return optional<U>{};
252 }
253#endif
254 }
255
256 /**
257 * 'Constructor' taking const RHS_CONVERTIBLE_TO_OPTIONAL_OF_T* is to allow easier interoperability
258 * with code that uses null-pointers to mean 'is-missing': nullptr means missing, and if non null,
259 * dereference and copy.
260 *
261 * \par Example Usage
262 * \code
263 * float* d1 = nullptr;
264 * double* d2 = nullptr;
265 * EXPECT_TRUE (not OptionalFromNullable (d1).has_value ());
266 * EXPECT_TRUE (not OptionalFromNullable (d2).has_value ());
267 * \endcode
268 */
269 template <typename RHS_CONVERTIBLE_TO_OPTIONAL_OF_T, constructible_from<RHS_CONVERTIBLE_TO_OPTIONAL_OF_T> T = RHS_CONVERTIBLE_TO_OPTIONAL_OF_T>
270 constexpr optional<T> OptionalFromNullable (const RHS_CONVERTIBLE_TO_OPTIONAL_OF_T* from);
271
272 /**
273 * if lhs and rhs engaged, this returns *lhs + *rhs, and otherwise nullopt
274 *
275 * \note this used to use AccumulateIf() before Stroika 2.1b12, but that produced confusing results. This is
276 * slightly safer, I think, and if you want the AccumulateIf () semantics, call AccumulateIf()
277 */
278 template <typename T>
279 optional<T> operator+ (const optional<T>& lhs, const optional<T>& rhs);
280 template <typename T>
281 optional<T> operator+ (const optional<T>& lhs, const T& rhs);
282 template <typename T>
283 optional<T> operator+ (const T& lhs, const optional<T>& rhs);
284
285 /**
286 * if lhs and rhs engaged, this returns *lhs - *rhs, and otherwise nullopt
287 *
288 * \note this used to use AccumulateIf() before Stroika 2.1b12, but that produced confusing results. This is
289 * slightly safer, I think, and if you want the AccumulateIf () semantics, call AccumulateIf()
290 */
291 template <typename T>
292 optional<T> operator- (const optional<T>& lhs, const optional<T>& rhs);
293 template <typename T>
294 optional<T> operator- (const optional<T>& lhs, const T& rhs);
295 template <typename T>
296 optional<T> operator- (const T& lhs, const optional<T>& rhs);
297
298 /**
299 * if lhs and rhs engaged, this returns *lhs * *rhs, and otherwise nullopt
300 *
301 * \note this used to use AccumulateIf() before Stroika 2.1b12, but that produced confusing results. This is
302 * slightly safer, I think, and if you want the AccumulateIf () semantics, call AccumulateIf()
303 */
304 template <typename T>
305 optional<T> operator* (const optional<T>& lhs, const optional<T>& rhs);
306 template <typename T>
307 optional<T> operator* (const optional<T>& lhs, const T& rhs);
308 template <typename T>
309 optional<T> operator* (const T& lhs, const optional<T>& rhs);
310
311 /**
312 * if lhs and rhs engaged, this returns *lhs / *rhs, and otherwise nullopt
313 *
314 * \note this used to use AccumulateIf() before Stroika 2.1b12, but that produced confusing results. This is
315 * slightly safer, I think, and if you want the AccumulateIf () semantics, call AccumulateIf()
316 */
317 template <typename T>
318 optional<T> operator/ (const optional<T>& lhs, const optional<T>& rhs);
319 template <typename T>
320 optional<T> operator/ (const optional<T>& lhs, const T& rhs);
321 template <typename T>
322 optional<T> operator/ (const T& lhs, const optional<T>& rhs);
323
324}
325
326/*
327 ********************************************************************************
328 ***************************** Implementation Details ***************************
329 ********************************************************************************
330 */
331#include "Optional.inl"
332
333#endif /*_Stroika_Foundation_Memory_Optional_h_*/
optional< OUT_T > OptionallyCopy(const optional< IN_T > &in)
if you can copy an IN_T to an OUT_T, you should be able to copy an optional<IN_T> to an optional<OUT_...
Definition Optional.inl:180
constexpr auto And_Then(const optional< T > &o, F &&f)
Definition Optional.h:207
const OT & NullCoalesce(const OT &l, const OT &r)
return one of l, or r, with first preference for which is engaged, and second preference for left-to-...
Definition Optional.inl:134
constexpr auto Or_Else(const optional< T > &o, F &&f)
Definition Optional.h:224
constexpr auto Transform(const optional< T > &o, F &&f)
Definition Optional.h:241
void AccumulateIf(optional< T > *lhsOptionalValue, const optional< CONVERTIBLE_TO_T > &rhsOptionalValue, const OP &op=OP{})
AccumulateIf () add in the rhs argument value to lhs optional, but if both were missing leave 'lhs' a...
Definition Optional.inl:28
constexpr const T & ValueOf(const optional< T > &t)
Same as *t, but Requires that 't' is engaged.
Definition Optional.inl:156
void CopyToIf(CONVERTABLE_TO_TYPE *to, const optional< T > &copyFromIfHasValue)
Definition Optional.inl:110