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