Stroika Library 3.0d25
 
Loading...
Searching...
No Matches
StdCompat.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_Common_StdCompat_h_
5#define _Stroika_Foundation_Common_StdCompat_h_ 1
6
7#include "Stroika/Foundation/StroikaPreComp.h"
8
9#include <bit>
10#include <chrono>
11#include <cmath>
12#include <compare>
13#include <concepts>
14#include <cstdarg>
15#include <ranges>
16
17#if defined(__cpp_lib_expected)
18#include <expected>
19#else
20#include <variant>
21#endif
22
23// Various kooky constraints
24// (1) clang++15/16 don't set __cpp_lib_format, so cannot check __cpp_lib_format >= 201907 instead check __has_include(<format>)
25// (2) has_include <format> false positives on some versions of XCode, and no reason to even build qStroika_HasComponent_fmtlib unless
26// its needed, so check it first
27
28#if qStroika_HasComponent_fmtlib
29#include <fmt/chrono.h>
30#include <fmt/format.h>
31#include <fmt/xchar.h>
32#elif __has_include(<format>)
33#include <format>
34#endif
35
36/**
37 * \file
38 *
39 * The purpose of this module is to define any std c++ functions/classes etc - which may not be provided by the
40 * current std c++ library (often because not compiled with appropriate --std=... flag) - and/or because its
41 * an old compiler.
42 *
43 * This also includes 'named requirements' - rendered as concepts (probably SHOULD be part of CPP standard but are not).
44 *
45 * This doesn't strictly violate any rules about sticking stuff into namespace std - cuz we don't. That's why we use a
46 * separate namespace (that often just indirects to the namespace std - where the function/class is defined already).
47 *
48 * A thin layer over the standard library: it includes nothing of Stroika's (beyond StroikaPreComp.h), so it is usable
49 * wherever std is.
50 *
51 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
52 *
53 */
54
55namespace Stroika::Foundation::Common::StdCompat {
56
57 /**
58 * By default include all of std, but throw in selected missing things from some implementations.
59 */
60 using namespace std;
61
62 /**
63 * \brief Logically the C++ standard BasicLockable named requirement, but that was not included in std c++ library
64 *
65 * Must be satisfied by: std::mutex, std::recursive_mutex, std::timed_mutex, std::shared_mutex,
66 * std::unique_lock<std::mutex>
67 * NOT satisfied by: std::lock_guard<std::mutex> - it is a scope guard, and exposes no
68 * lock ()/unlock () of its own
69 *
70 * \note The examples above, and that this concept is not vacuously true, are asserted in
71 * StdCompat.cpp. They live in a .cpp so the <mutex>/<shared_mutex> needed to name the types
72 * costs one translation unit rather than every one that includes this header.
73 *
74 * \see https://en.cppreference.com/w/cpp/named_req/BasicLockable.html
75 */
76 template <typename T>
77 concept BasicLockable = requires (T lo) {
78 // Requires a public lock() member function
79 { lo.lock () } -> std::same_as<void>;
80 // Requires a public unlock() member function
81 { lo.unlock () } -> std::same_as<void>;
82 };
83
84 /**
85 * \brief Logically the C++ standard Lockable named requirement, but that was not included in std c++ library
86 *
87 * Must be satisfied by: std::mutex, std::recursive_mutex, std::timed_mutex, std::shared_mutex
88 *
89 * \note Asserted in StdCompat.cpp - including the check that matters most, that this concept is
90 * STRICTLY STRONGER than BasicLockable. That needs a purpose-built type: every std lockable
91 * type offers try_lock () too, so no standard type could ever tell the two apart.
92 *
93 * \see https://en.cppreference.com/w/cpp/named_req/Lockable.html
94 */
95 template <typename T>
96 concept Lockable = BasicLockable<T> and requires (T lo) {
97 { lo.try_lock () } -> std::same_as<bool>;
98 };
99
100 /**
101 * \brief Logically the C++ standard TimedLockable named requirement, but that was not included in std c++ library
102 *
103 * The TimedMutex requirements extend the TimedLockable requirements to include inter-thread synchronization
104 *
105 * Must be satisfied by: std::timed_mutex, std::recursive_timed_mutex
106 * NOT satisfied by: std::mutex - it has no try_lock_for ()/try_lock_until ()
107 *
108 * \note Asserted in StdCompat.cpp, along with the check that this is strictly stronger than
109 * Lockable. @see the note on BasicLockable for why those live in a .cpp.
110 *
111 * \see https://en.cppreference.com/w/cpp/named_req/TimedLockable.html
112 */
113 template <typename T>
114 concept TimedLockable = Lockable<T> and requires (T lo, const chrono::milliseconds& d, const chrono::time_point<chrono::steady_clock>& tp) {
115 // Requires a public try_lock_for() taking a duration
116 { lo.try_lock_for (d) } -> std::same_as<bool>;
117 // Requires a public try_lock_until() taking a time_point
118 { lo.try_lock_until (tp) } -> std::same_as<bool>;
119 };
120
121 /**
122 * \brief Logically the C++ standard TimedMutex named requirement, but that was not included in std c++ library
123 *
124 * A TimedMutex is a Mutex which also satisfies TimedLockable - it adds to TimedLockable that the type
125 * is default-constructible and neither copyable nor movable, as every std mutex type is.
126 *
127 * Must be satisfied by: std::timed_mutex, std::recursive_timed_mutex
128 * NOT satisfied by: std::mutex (it is not TimedLockable)
129 *
130 * \note Asserted in StdCompat.cpp, together with 'not TimedMutex<int>' - which is there to prove
131 * the concept is not vacuously true, the way a botched requires-clause silently would be.
132 *
133 * \see https://en.cppreference.com/w/cpp/named_req/TimedMutex.html
134 */
135 template <typename T>
136 concept TimedMutex =
137 TimedLockable<T> and default_initializable<T> and destructible<T> and not copy_constructible<T> and not move_constructible<T>;
138
139 namespace Private_ {
140 template <class _Ty>
141 concept _Boolean_testable_impl = convertible_to<_Ty, bool>;
142 }
143
144 /**
145 * \brief handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in exposition).
146 *
147 * \see https://en.cppreference.com/w/cpp/concepts/boolean-testable.html
148 */
149 template <class _Ty>
150 concept Boolean_testable = Private_::_Boolean_testable_impl<_Ty> && requires (_Ty&& __t) {
151 { !static_cast<_Ty&&> (__t) } -> Private_::_Boolean_testable_impl;
152 };
153
154 /**
155 * \brief like convertible_to, but also handling cases where T has an explicit CTOR taking From
156 *
157 * \see https://stackoverflow.com/questions/76547398/stdconvertible-to-failing-to-recognize-explicitly-convertible-types
158 */
159 template <class FROM, class TO>
160 concept explicitly_convertible_to = requires { static_cast<TO> (declval<FROM> ()); };
161
162#if qStroika_HasComponent_fmtlib
163#define qStroika_Foundation_Characters_FMT_PREFIX_ fmt
164#elif __has_include(<format>)
165#define qStroika_Foundation_Characters_FMT_PREFIX_ std
166#else
167 static_assert (false, "Stroika v3 requires some std::format compatible library - if building with one lacking builtin std::format, "
168 "configure --fmtlib use");
169#endif
170
171 /**
172 * To allow interop between std::format and fmt(fmtlib)::format, publish the names into the namespace 'Stroika::Foundation::Common::StdCompat' and use those.
173 * Lose this once I can fully depend upon std::format... --LGP 2024-03-12
174 */
175 using qStroika_Foundation_Characters_FMT_PREFIX_::basic_format_parse_context;
176 using qStroika_Foundation_Characters_FMT_PREFIX_::format;
177 using qStroika_Foundation_Characters_FMT_PREFIX_::format_args;
178 using qStroika_Foundation_Characters_FMT_PREFIX_::format_error;
179 using qStroika_Foundation_Characters_FMT_PREFIX_::format_string;
180 using qStroika_Foundation_Characters_FMT_PREFIX_::format_to;
181 using qStroika_Foundation_Characters_FMT_PREFIX_::make_format_args;
182 using qStroika_Foundation_Characters_FMT_PREFIX_::make_wformat_args;
183 using qStroika_Foundation_Characters_FMT_PREFIX_::vformat;
184 using qStroika_Foundation_Characters_FMT_PREFIX_::wformat_args;
185 using qStroika_Foundation_Characters_FMT_PREFIX_::wformat_string;
186
187#if qStroika_Foundation_Common_cplusplus >= qStroika_Foundation_Common_cplusplus_23
188 template <class T, class CharT>
189 concept formattable = std::formattable<T, CharT>;
190#else
191 namespace Private_ {
192 template <class _CharT>
193 struct _Phony_fmt_iter_for {
194 using difference_type = ptrdiff_t;
195 _CharT& operator* () const;
196 _Phony_fmt_iter_for& operator++ ();
197 _Phony_fmt_iter_for operator++ (int);
198 };
199 // _Formatter a defaulted template parameter, not named in the requires: needed for clang++-15 (compiler bug but no BWA declaration
200 // for now...BWA in middle of BWA ;-)). And formatter<T, char_type> - which a context's formatter_type<T> is by definition - as
201 // fmtlib's context (as of fmtlib 11) has no formatter_type.
202 template <class _Ty, class _Context, class _Formatter = qStroika_Foundation_Characters_FMT_PREFIX_::formatter<remove_const_t<_Ty>, typename _Context::char_type>>
203 concept _Formattable_with = semiregular<_Formatter> && requires (_Formatter& __f, const _Formatter& __cf, _Ty&& __t, _Context __fc,
204 basic_format_parse_context<typename _Context::char_type> __pc) {
205 { __f.parse (__pc) } -> same_as<typename decltype (__pc)::iterator>;
206 { __cf.format (__t, __fc) } -> same_as<typename _Context::iterator>;
207 };
208 }
209 template <class T, class CharT>
210 concept formattable =
211 Private_::_Formattable_with<remove_reference_t<T>, qStroika_Foundation_Characters_FMT_PREFIX_::basic_format_context<Private_::_Phony_fmt_iter_for<CharT>, CharT>>;
212#endif
213
214 /**
215 * Kept so existing callers of StdCompat::bit_cast still compile; every supported standard library now has std::bit_cast
216 * (the fallback was for XCode 14).
217 */
218 using std::bit_cast;
219
220 /**
221 * std::byteswap where the standard library has it (it is C++23), else an equivalent - so every C++20 build uses
222 * the version below.
223 */
224#if defined(__cpp_lib_byteswap)
225 using std::byteswap;
226#else
227 template <class T>
228 inline T byteswap (T n) noexcept
229 {
230 static_assert (std::has_unique_object_representations_v<T>, "T may not have padding bits");
231 auto value_representation = bit_cast<array<byte, sizeof (T)>> (n);
232 for (size_t i = 0; i < value_representation.size () / 2; ++i) {
233 swap (value_representation[i], value_representation[value_representation.size () - 1 - i]);
234 }
235 return bit_cast<T> (value_representation);
236 }
237#endif
238
239 /**
240 * std::isinf, but constexpr where the standard library allows it (C++23)
241 */
242 template <typename T>
243#if qStroika_Foundation_Common_cplusplus >= qStroika_Foundation_Common_cplusplus_23
244 constexpr
245#else
246 inline
247#endif
248 bool isinf (T v) noexcept
249 {
250 return std::isinf (v);
251 }
252
253 /**
254 * std::isnan, but constexpr where the standard library allows it (C++23)
255 */
256 template <typename T>
257#if qStroika_Foundation_Common_cplusplus >= qStroika_Foundation_Common_cplusplus_23
258 constexpr
259#else
260 inline
261#endif
262 bool isnan (T v) noexcept
263 {
264 return std::isnan (v);
265 }
266
267#if qCompilerAndStdLib_stdlib_compare_three_way_present_but_Buggy
268 struct compare_three_way {
269 // NOTE - this workaround is GENERALLY INADEQUATE, but is adequate for my current use in Stroika -- LGP 2022-11-01
270 template <typename LT, typename RT>
271 constexpr auto operator() (LT&& lhs, RT&& rhs) const
272 {
273 using CT = common_type_t<LT, RT>;
274 if (equal_to<CT>{}(forward<LT> (lhs), forward<RT> (rhs))) {
275 return strong_ordering::equal;
276 }
277 return less<CT>{}(forward<LT> (lhs), forward<RT> (rhs)) ? strong_ordering::less : strong_ordering::greater;
278 }
279 using is_transparent = void;
280 };
281#else
282 using compare_three_way = std::compare_three_way;
283#endif
284
285 /**
286 * Wrap a simplified version of std::unexpected, cuz handy even if c++23 not present
287 */
288#if defined(__cpp_lib_expected)
289 template <typename T>
290 using unexpected = std::unexpected<T>;
291 template <typename T, typename E>
292 using expected = std::expected<T, E>;
293#else
294 template <typename T>
296 public:
297 template <typename _UError = T>
298 requires (!is_same_v<remove_cvref_t<_UError>, unexpected> && !is_same_v<remove_cvref_t<_UError>, in_place_t> && is_constructible_v<T, _UError>)
299 constexpr explicit unexpected (_UError&& _Unex)
300 : _Unexpected (forward<_UError> (_Unex))
301 {
302 }
303 template <typename... _Args>
304 requires is_constructible_v<T, _Args...>
305 constexpr explicit unexpected (in_place_t, _Args&&... _Vals)
306 : _Unexpected (forward<_Args> (_Vals)...)
307 {
308 }
309 template <typename _Uty, typename... _Args>
310 requires is_constructible_v<T, initializer_list<_Uty>&, _Args...>
311 constexpr explicit unexpected (in_place_t, initializer_list<_Uty> _Ilist, _Args&&... _Vals)
312 : _Unexpected (_Ilist, forward<_Args> (_Vals)...)
313 {
314 }
315
316 constexpr const T& error () const& noexcept
317 {
318 return _Unexpected;
319 }
320 constexpr T& error () & noexcept
321 {
322 return _Unexpected;
323 }
324 constexpr const T&& error () const&& noexcept
325 {
326 return std::move (_Unexpected);
327 }
328 constexpr T&& error () && noexcept
329 {
330 return std::move (_Unexpected);
331 }
332
333 constexpr void swap (unexpected& _Other)
334 {
335 using std::swap;
336 swap (_Unexpected, _Other._Unexpected); // intentional ADL
337 }
338
339 friend constexpr void swap (unexpected& _Left, unexpected& _Right)
340 requires is_swappable<T>::value // TRANSITION, /permissive needs ::value
341 {
342 _Left.swap (_Right);
343 }
344
345 template <class _UErr>
346 friend constexpr bool operator== (const unexpected& _Left, const unexpected<_UErr>& _Right)
347 {
348 return _Left._Unexpected == _Right.error ();
349 }
350
351 private:
352 T _Unexpected;
353 };
354 template <typename T>
356
357 /**
358 * Wrap a simplified version of std::expected, cuz handy even if c++23 not present
359 *
360 * \note STILL VERY ROUGH DRAFT - IN USE - BUT NOT SURE ITS RIGHT (cuz mine works but delegating to
361 * MSFT one not working, so maybe mine wrong).
362 */
363 template <typename T, typename E>
364 class expected;
365 template <typename T, typename E>
366 class expected {
367 public:
368 using value_type = T;
369 using error_type = E;
371
372 constexpr expected () noexcept = default;
373 constexpr expected (const expected&) noexcept = default;
374 constexpr expected (T v)
375 : fData_{v}
376 {
377 }
378 constexpr expected (const unexpected_type& e)
379 : fData_{e.error ()}
380 {
381 }
382 template <typename T1, typename E1>
383 constexpr expected (const expected<T1, E1>& e)
384 {
385 if (e) {
386 fData_ = e.value ();
387 }
388 else {
389 fData_ = e.error ();
390 }
391 }
392 explicit operator bool () const noexcept
393 {
394 return std::get_if<T> (&fData_) != nullptr;
395 }
396 T operator* () const
397 {
398 return value ();
399 }
400 T value () const
401 {
402 return get<T> (fData_);
403 }
404 E error () const
405 {
406 return get<E> (fData_);
407 }
408
409 private:
410 variant<T, E> fData_;
411 };
412#endif
413
414 /**
415 * \brief Stroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is intentionally uninitialized
416 *
417 * [[indeterminate]]
418 * https://en.cppreference.com/w/cpp/language/attributes/indeterminate.html
419 *
420 * \par Example Usage
421 * \code
422 * Stroika_ATTRIBUTE_INDETERMINATE byte r[1024]; // don't initialize explicitly cuz filled in below before used (performance)
423 * \endcode
424 */
425#if __has_cpp_attribute(indeterminate)
426#define Stroika_ATTRIBUTE_INDETERMINATE [[indeterminate]]
427#else
428#define Stroika_ATTRIBUTE_INDETERMINATE
429#endif
430
431 /**
432 * \brief Stroika_ATTRIBUTE_ASSUME(C) is used where you would put a C++ attribute assume expression, to assume a condition is true for a given block.
433 *
434 * The assume attribute was introduced in c++23, and Stroika OPTIONALLY supports this, but doesn't require it as of Stroika v3.
435 * So use Stroika_ATTRIBUTE_ASSUME () to conditionally use [[assume(X)]]
436 */
437#if __has_cpp_attribute(assume) && defined(__clang__)
438 // clang warns by default (-Wassume) whenever it ignores an assumption whose expression might have side effects, such as
439 // a function call. That helps someone who wrote [[assume]] by hand for its effect; for the Assert/Require/Ensure that
440 // expand to this - conditions written as documentation first - it is only noise. So silence it for these expansions
441 // only, not for other code's own [[assume]]s.
442#define Stroika_ATTRIBUTE_ASSUME(X) \
443 _Pragma ("clang diagnostic push") _Pragma ("clang diagnostic ignored \"-Wassume\"") [[assume (X)]]; \
444 _Pragma ("clang diagnostic pop")
445#elif __has_cpp_attribute(assume)
446#define Stroika_ATTRIBUTE_ASSUME(X) [[assume (X)]];
447#elif _MSC_VER
448 // Docs not clear.
449 // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1774r4.pdf suggests this hack. BUT...
450 // https://github.com/MicrosoftDocs/cpp-docs/blob/main/docs/build/optimization-best-practices.md seems to hint __assume doesn't evaluate X, except to pay attention to simple a>constant compares so this should be OK
451#define Stroika_ATTRIBUTE_ASSUME(X) __assume (X);
452#else
453#define Stroika_ATTRIBUTE_ASSUME(X)
454#endif
455
456 /**
457 * \brief Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS - used for the attribute [[no_unique_address]]
458 *
459 * MSVC accepts [[no_unique_address]] but ignores it, to keep its ABI, and supports [[msvc::no_unique_address]] instead
460 * (see qCompilerAndStdLib_NO_UNIQUE_ADDR_IgnoredAndMustUseMSVCNOUNIQUE_Buggy)
461 */
462#if qCompilerAndStdLib_NO_UNIQUE_ADDR_IgnoredAndMustUseMSVCNOUNIQUE_Buggy && defined(_MSC_VER)
463#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
464#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
465#else
466#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
467#endif
468#elif __has_cpp_attribute(no_unique_address)
469#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[no_unique_address]]
470#else
471#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
472#endif
473
474 /**
475 * \brief [[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this
476 * on things where its not broken.
477 */
478#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
479#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE [[msvc::no_unique_address]]
480#else
481#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
482#endif
483
484 /**
485 * \brief Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY same as Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
486 *
487 * EMPHASIZE that this is Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
488 * and not Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE cuz its known buggy
489 */
490#define Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
491
492// DEPRECATED NAMES - since 3.0d25 (to be removed in v3.0a1): these expand to attributes, not values, so by
493// Design-Overview.md "Macro names" they take the Stroika_ prefix, not q
494#define qStroika_ATTRIBUTE_INDETERMINATE Stroika_ATTRIBUTE_INDETERMINATE
495#define qStroika_ATTRIBUTE_ASSUME Stroika_ATTRIBUTE_ASSUME
496#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
497#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE
498#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY Stroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY
499
500}
501
502/*
503 ********************************************************************************
504 ***************************** Implementation Details ***************************
505 ********************************************************************************
506 */
507#include "StdCompat.inl"
508
509#endif /*_Stroika_Foundation_Common_StdCompat_h_*/
constexpr bool isnan(T v) noexcept
Definition StdCompat.h:262
constexpr bool isinf(T v) noexcept
Definition StdCompat.h:248
Logically the C++ standard BasicLockable named requirement, but that was not included in std c++ libr...
Definition StdCompat.h:77
handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in...
Definition StdCompat.h:150
Logically the C++ standard Lockable named requirement, but that was not included in std c++ library.
Definition StdCompat.h:96
Logically the C++ standard TimedLockable named requirement, but that was not included in std c++ libr...
Definition StdCompat.h:114
Logically the C++ standard TimedMutex named requirement, but that was not included in std c++ library...
Definition StdCompat.h:136
like convertible_to, but also handling cases where T has an explicit CTOR taking From
Definition StdCompat.h:160
STL namespace.