Stroika Library 3.0d24
 
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 <cmath>
11#include <compare>
12#include <concepts>
13#include <cstdarg>
14#include <ranges>
15
16#if __cpp_lib_expected
17#include <expected>
18#else
19#include <variant>
20#endif
21
22// Various kooky constraints
23// (1) clang++15/16 don't set __cpp_lib_format, so cannot check __cpp_lib_format >= 201907 instead check __has_include(<format>)
24// (2) has_include <format> false positives on some versions of XCode, and no reason to even build qStroika_HasComponent_fmtlib unless
25// its needed, so check it first
26
27#if qStroika_HasComponent_fmtlib
28#include <fmt/chrono.h>
29#include <fmt/format.h>
30#include <fmt/xchar.h>
31#elif __has_include(<format>)
32#include <format>
33#endif
34
35/**
36 * \file
37 *
38 * The purpose of this module is to define any std c++ functions/classes etc - which may not be provided by the
39 * current std c++ library (often because not compiled with appropriate --std=... flag) - and/or because its
40 * an old compiler.
41 *
42 * This also includes 'named requirements' - rendered as concepts (probably SHOULD be part of CPP standard but are not).
43 *
44 * This doesn't strictly violate any rules about sticking stuff into namespace std - cuz we don't. That's why we use a
45 * separate namespace (that often just indirects to the namespace std - where the function/class is defined already).
46 *
47 * \note Code-Status: <a href="Code-Status.md#Beta">Beta</a>
48 *
49 */
50
51namespace Stroika::Foundation::Common::StdCompat {
52
53 /**
54 * By default include all of std, but throw in selected missing things from some implementations.
55 */
56 using namespace std;
57
58 /**
59 * \brief Logically the C++ standard BasicLockable named requirement, but that was not included in std c++ library
60 *
61 * \see https://en.cppreference.com/w/cpp/named_req/BasicLockable.html
62 */
63 template <typename T>
64 concept BasicLockable = requires (T lo) {
65 // Requires a public lock() member function
66 { lo.lock () } -> std::same_as<void>;
67 // Requires a public unlock() member function
68 { lo.unlock () } -> std::same_as<void>;
69 };
70
71 /**
72 * \brief Logically the C++ standard Lockable named requirement, but that was not included in std c++ library
73 *
74 * \see https://en.cppreference.com/w/cpp/named_req/Lockable.html
75 */
76 template <typename T>
77 concept Lockable = BasicLockable<T> and requires (T lo) {
78 { lo.try_lock () } -> std::same_as<bool>;
79 };
80
81 namespace Private_ {
82 template <class _Ty>
83 concept _Boolean_testable_impl = convertible_to<_Ty, bool>;
84 }
85
86 /**
87 * \brief handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in exposition).
88 *
89 * \see https://en.cppreference.com/w/cpp/concepts/boolean-testable.html
90 */
91 template <class _Ty>
92 concept Boolean_testable = Private_::_Boolean_testable_impl<_Ty> && requires (_Ty&& __t) {
93 { !static_cast<_Ty&&> (__t) } -> Private_::_Boolean_testable_impl;
94 };
95
96 /**
97 * \brief like convertible_to, but also handling cases where T has an explicit CTOR taking From
98 *
99 * \see https://stackoverflow.com/questions/76547398/stdconvertible-to-failing-to-recognize-explicitly-convertible-types
100 */
101 template <class FROM, class TO>
102 concept explicitly_convertible_to = requires { static_cast<TO> (declval<FROM> ()); };
103
104#if qStroika_HasComponent_fmtlib
105#define qStroika_Foundation_Characters_FMT_PREFIX_ fmt
106#elif __has_include(<format>)
107#define qStroika_Foundation_Characters_FMT_PREFIX_ std
108#else
109 static_assert (false, "Stroika v3 requires some std::format compatible library - if building with one lacking builtin std::format, "
110 "configure --fmtlib use");
111#endif
112
113 /**
114 * To allow interop between std::format and fmt(fmtlib)::format, publish the names into the namespace 'Stroika::Foundation::Common::StdCompat' and use those.
115 * Lose this once I can fully depend upon std::format... --LGP 2024-03-12
116 */
117 using qStroika_Foundation_Characters_FMT_PREFIX_::basic_format_parse_context;
118 using qStroika_Foundation_Characters_FMT_PREFIX_::format;
119 using qStroika_Foundation_Characters_FMT_PREFIX_::format_args;
120 using qStroika_Foundation_Characters_FMT_PREFIX_::format_error;
121 using qStroika_Foundation_Characters_FMT_PREFIX_::format_string;
122 using qStroika_Foundation_Characters_FMT_PREFIX_::format_to;
123 using qStroika_Foundation_Characters_FMT_PREFIX_::make_format_args;
124 using qStroika_Foundation_Characters_FMT_PREFIX_::make_wformat_args;
125 using qStroika_Foundation_Characters_FMT_PREFIX_::vformat;
126 using qStroika_Foundation_Characters_FMT_PREFIX_::wformat_args;
127 using qStroika_Foundation_Characters_FMT_PREFIX_::wformat_string;
128
129#if __cplusplus >= kStrokia_Foundation_Common_cplusplus_23 || _HAS_CXX23 /*vis studio uses _HAS_CXX23 */
130 template <class T, class CharT>
131 concept formattable = std::formattable<T, CharT>;
132#else
133 namespace Private_ {
134 template <class _CharT>
135 struct _Phony_fmt_iter_for {
136 using difference_type = ptrdiff_t;
137 _CharT& operator* () const;
138 _Phony_fmt_iter_for& operator++ ();
139 _Phony_fmt_iter_for operator++ (int);
140 };
141 // _Formatter = typename _Context needed for clang++-15 (compiler bug but no BWA declaration for now...BWA in middle of BWA ;-))
142 template <class _Ty, class _Context, class _Formatter = typename _Context::template formatter_type<remove_const_t<_Ty>>>
143 concept _Formattable_with = semiregular<_Formatter> && requires (_Formatter& __f, const _Formatter& __cf, _Ty&& __t, _Context __fc,
144 basic_format_parse_context<typename _Context::char_type> __pc) {
145 { __f.parse (__pc) } -> same_as<typename decltype (__pc)::iterator>;
146 { __cf.format (__t, __fc) } -> same_as<typename _Context::iterator>;
147 };
148 }
149 template <class T, class CharT>
150 concept formattable =
151 Private_::_Formattable_with<remove_reference_t<T>, qStroika_Foundation_Characters_FMT_PREFIX_::basic_format_context<Private_::_Phony_fmt_iter_for<CharT>, CharT>>;
152#endif
153
154 /**
155 * Workaround absence of bit_cast in MacOS XCode 14 (which we support with Stroika v3)
156 */
157#if __cpp_lib_bit_cast >= 201806L
158 using std::bit_cast;
159#else
160 template <class To, class From>
161 inline To bit_cast (const From& src) noexcept
162 requires (sizeof (To) == sizeof (From) && std::is_trivially_copyable_v<From> && std::is_trivially_copyable_v<To>)
163 {
164 static_assert (std::is_trivially_constructible_v<To>, "This implementation additionally requires "
165 "destination type to be trivially constructible");
166 To dst;
167 std::memcpy (&dst, &src, sizeof (To));
168 return dst;
169 }
170#endif
171
172 /**
173 * Workaround absence of byteswap gcc up to version 12, and clang (up to 14).
174 */
175#if __cpp_lib_byteswap >= 202110L
176 using std::byteswap;
177#else
178 template <class T>
179 inline T byteswap (T n) noexcept
180 {
181 DISABLE_COMPILER_GCC_WARNING_START ("GCC diagnostic ignored \"-Warray-bounds\"");
182 static_assert (std::has_unique_object_representations_v<T>, "T may not have padding bits");
183 auto value_representation = bit_cast<array<byte, sizeof (T)>> (n);
184 for (size_t i = 0; i < value_representation.size () / 2; ++i) {
185 swap (value_representation[i], value_representation[value_representation.size () - i]);
186 }
187 return bit_cast<T> (value_representation);
188 DISABLE_COMPILER_GCC_WARNING_END ("GCC diagnostic ignored \"-Warray-bounds\"");
189 }
190#endif
191
192 /**
193 * workaround qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
194 */
195 template <typename T>
196#if __cplusplus >= kStrokia_Foundation_Common_cplusplus_23 || _MSVC_LANG >= kStrokia_Foundation_Common_cplusplus_23
197 constexpr
198#else
199 inline
200#endif
201 bool isinf (T v) noexcept
202 {
203#if qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
204 if constexpr (integral<T>) {
205 return false; // needed for vis stud
206 }
207 else
208#endif
209 return std::isinf (v);
210 }
211
212 /**
213 * workaround qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
214 */
215 template <typename T>
216#if __cplusplus >= kStrokia_Foundation_Common_cplusplus_23
217 constexpr
218#else
219 inline
220#endif
221 bool isnan (T v) noexcept
222 {
223#if qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
224 if constexpr (integral<T>) {
225 return false; // needed for vis stud
226 }
227 else
228#endif
229 return std::isnan (v);
230 }
231
232#if qCompilerAndStdLib_stdlib_compare_three_way_present_but_Buggy
233 struct compare_three_way {
234 // NOTE - this workaround is GENERALLY INADEQUATE, but is adequate for my current use in Stroika -- LGP 2022-11-01
235 template <typename LT, typename RT>
236 constexpr auto operator() (LT&& lhs, RT&& rhs) const
237 {
238 using CT = common_type_t<LT, RT>;
239 if (equal_to<CT>{}(forward<LT> (lhs), forward<RT> (rhs))) {
240 return strong_ordering::equal;
241 }
242 return less<CT>{}(forward<LT> (lhs), forward<RT> (rhs)) ? strong_ordering::less : strong_ordering::greater;
243 }
244 using is_transparent = void;
245 };
246#else
247 using compare_three_way = std::compare_three_way;
248#endif
249
250 /**
251 * Wrap a simplified version of std::unexpected, cuz handy even if c++23 not present
252 */
253#if __cpp_lib_expected
254 template <typename T>
255 using unexpected = std::unexpected<T>;
256 template <typename T, typename E>
257 using expected = std::expected<T, E>;
258#else
259 template <typename T>
261 public:
262 template <typename _UError = T>
263 requires (!is_same_v<remove_cvref_t<_UError>, unexpected> && !is_same_v<remove_cvref_t<_UError>, in_place_t> && is_constructible_v<T, _UError>)
264 constexpr explicit unexpected (_UError&& _Unex)
265 : _Unexpected (forward<_UError> (_Unex))
266 {
267 }
268 template <typename... _Args>
269 requires is_constructible_v<T, _Args...>
270 constexpr explicit unexpected (in_place_t, _Args&&... _Vals)
271 : _Unexpected (forward<_Args> (_Vals)...)
272 {
273 }
274 template <typename _Uty, typename... _Args>
275 requires is_constructible_v<T, initializer_list<_Uty>&, _Args...>
276 constexpr explicit unexpected (in_place_t, initializer_list<_Uty> _Ilist, _Args&&... _Vals)
277 : _Unexpected (_Ilist, forward<_Args> (_Vals)...)
278 {
279 }
280
281 constexpr const T& error () const& noexcept
282 {
283 return _Unexpected;
284 }
285 constexpr T& error () & noexcept
286 {
287 return _Unexpected;
288 }
289 constexpr const T&& error () const&& noexcept
290 {
291 return std::move (_Unexpected);
292 }
293 constexpr T&& error () && noexcept
294 {
295 return std::move (_Unexpected);
296 }
297
298 constexpr void swap (unexpected& _Other)
299 {
300 using std::swap;
301 swap (_Unexpected, _Other._Unexpected); // intentional ADL
302 }
303
304 friend constexpr void swap (unexpected& _Left, unexpected& _Right)
305 requires is_swappable<T>::value // TRANSITION, /permissive needs ::value
306 {
307 _Left.swap (_Right);
308 }
309
310 template <class _UErr>
311 friend constexpr bool operator== (const unexpected& _Left, const unexpected<_UErr>& _Right)
312 {
313 return _Left._Unexpected == _Right.error ();
314 }
315
316 private:
317 T _Unexpected;
318 };
319 template <typename T>
321
322 /**
323 * Wrap a simplified version of std::expected, cuz handy even if c++23 not present
324 *
325 * \note STILL VERY ROUGH DRAFT - IN USE - BUT NOT SURE ITS RIGHT (cuz mine works but delegating to
326 * MSFT one not working, so maybe mine wrong).
327 */
328 template <typename T, typename E>
329 class expected;
330 template <typename T, typename E>
331 class expected {
332 public:
333 using value_type = T;
334 using error_type = E;
336
337 constexpr expected () noexcept = default;
338 constexpr expected (const expected&) noexcept = default;
339 constexpr expected (T v)
340 : fData_{v}
341 {
342 }
343 constexpr expected (const unexpected_type& e)
344 : fData_{e.error ()}
345 {
346 }
347 template <typename T1, typename E1>
348 constexpr expected (const expected<T1, E1>& e)
349 {
350 if (e) {
351 fData_ = e.value ();
352 }
353 else {
354 fData_ = e.error ();
355 }
356 }
357 explicit operator bool () const noexcept
358 {
359 return std::get_if<T> (&fData_) != nullptr;
360 }
361 T operator* () const
362 {
363 return value ();
364 }
365 T value () const
366 {
367 return get<T> (fData_);
368 }
369 E error () const
370 {
371 return get<E> (fData_);
372 }
373
374 private:
375 variant<T, E> fData_;
376 };
377#endif
378
379 /**
380 * \brief qStroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is intentionally uninitialized
381 *
382 * [[indeterminate]]
383 * https://en.cppreference.com/w/cpp/language/attributes/indeterminate.html
384 *
385 * \par Example Usage
386 * \code
387 * qStroika_ATTRIBUTE_INDETERMINATE byte r[1024]; // don't initialize explicitly cuz filled in below before used (performance)
388 * \endcode
389 */
390#if __has_cpp_attribute(indeterminate)
391#define qStroika_ATTRIBUTE_INDETERMINATE [[indeterminate]]
392#else
393#define qStroika_ATTRIBUTE_INDETERMINATE
394#endif
395
396 /**
397 * \brief qStroika_ATTRIBUTE_ASSUME(C) is used where you would put a C++ attribute assume expression, to assume a condition is true for a given block.
398 *
399 * The assume attribute was introduced in c++23, and Stroika OPTIONALLY supports this, but doesn't require it as of Stroika v3.
400 * So use qStroika_ATTRIBUTE_ASSUME () to conditionally use [[assume(X)]]
401 */
402#if __has_cpp_attribute(assume)
403#define qStroika_ATTRIBUTE_ASSUME(X) [[assume (X)]];
404#elif _MSC_VER
405 // Docs not clear.
406 // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1774r4.pdf suggests this hack. BUT...
407 // 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
408#define qStroika_ATTRIBUTE_ASSUME(X) __assume (X);
409#else
410#define qStroika_ATTRIBUTE_ASSUME(X)
411#endif
412
413#if qCompilerAndStdLib_AssumeWarningSpamming_Buggy
414 // INTENTIONALLY UNBALANCED WITH _END - cuz this is used all over the place!!!
415 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wassume\"");
416#endif
417
418 /**
419 * \brief qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS - used for the attribute [[no_unique_address]]
420 *
421 * Though all the compilers we target SOMEWHAT support this, MSVC appears to support it by ignoring it. Which is crazy
422 * cuz it DOES support msvc::no_unique_address
423 */
424#if qCompilerAndStdLib_NO_UNIQUE_ADDR_IgnoredAndMustUseMSVCNOUNIQUE_Buggy && defined(_MSC_VER)
425#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
426#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
427#else
428#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
429#endif
430#elif __has_cpp_attribute(no_unique_address)
431#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[no_unique_address]]
432#else
433#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
434#endif
435
436 /**
437 * \brief [[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this
438 * on things where its not broken.
439 */
440#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
441#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE [[msvc::no_unique_address]]
442#else
443#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
444#endif
445
446 /**
447 * \brief qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY same as qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
448 *
449 * EMPHASIZE that this is qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
450 * and not qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE cuz its known buggy
451 */
452#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
453
454}
455
456/*
457 ********************************************************************************
458 ***************************** Implementation Details ***************************
459 ********************************************************************************
460 */
461#include "StdCompat.inl"
462
463#endif /*_Stroika_Foundation_Common_StdCompat_h_*/
constexpr bool isnan(T v) noexcept
Definition StdCompat.h:221
To bit_cast(const From &src) noexcept
Definition StdCompat.h:161
constexpr bool isinf(T v) noexcept
Definition StdCompat.h:201
Logically the C++ standard BasicLockable named requirement, but that was not included in std c++ libr...
Definition StdCompat.h:64
handy re-usable concept, with the obvious meaning, and strangely omitted from std-c++ (though used in...
Definition StdCompat.h:92
Logically the C++ standard Lockable named requirement, but that was not included in std c++ library.
Definition StdCompat.h:77
like convertible_to, but also handling cases where T has an explicit CTOR taking From
Definition StdCompat.h:102
STL namespace.