Stroika Library 3.0d23
 
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
202 isinf (T v) noexcept
203 {
204#if qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
205 if constexpr (integral<T>) {
206 return false; // needed for vis stud
207 }
208 else
209#endif
210 return std::isinf (v);
211 }
212
213 /**
214 * workaround qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
215 */
216 template <typename T>
217#if __cplusplus >= kStrokia_Foundation_Common_cplusplus_23
218 constexpr
219#else
220 inline
221#endif
222 bool
223 isnan (T v) noexcept
224 {
225#if qCompilerAndStdLib_fpclasifyEtcOfInteger_Buggy
226 if constexpr (integral<T>) {
227 return false; // needed for vis stud
228 }
229 else
230#endif
231 return std::isnan (v);
232 }
233
234#if qCompilerAndStdLib_stdlib_compare_three_way_present_but_Buggy
235 struct compare_three_way {
236 // NOTE - this workaround is GENERALLY INADEQUATE, but is adequate for my current use in Stroika -- LGP 2022-11-01
237 template <typename LT, typename RT>
238 constexpr auto operator() (LT&& lhs, RT&& rhs) const
239 {
240 using CT = common_type_t<LT, RT>;
241 if (equal_to<CT>{}(forward<LT> (lhs), forward<RT> (rhs))) {
242 return strong_ordering::equal;
243 }
244 return less<CT>{}(forward<LT> (lhs), forward<RT> (rhs)) ? strong_ordering::less : strong_ordering::greater;
245 }
246 using is_transparent = void;
247 };
248#else
249 using compare_three_way = std::compare_three_way;
250#endif
251
252 /**
253 * Wrap a simplified version of std::unexpected, cuz handy even if c++23 not present
254 */
255#if __cpp_lib_expected
256 template <typename T>
257 using unexpected = std::unexpected<T>;
258 template <typename T, typename E>
259 using expected = std::expected<T, E>;
260#else
261 template <typename T>
263 public:
264 template <typename _UError = T>
265 requires (!is_same_v<remove_cvref_t<_UError>, unexpected> && !is_same_v<remove_cvref_t<_UError>, in_place_t> && is_constructible_v<T, _UError>)
266 constexpr explicit unexpected (_UError&& _Unex)
267 : _Unexpected (forward<_UError> (_Unex))
268 {
269 }
270 template <typename... _Args>
271 requires is_constructible_v<T, _Args...>
272 constexpr explicit unexpected (in_place_t, _Args&&... _Vals)
273 : _Unexpected (forward<_Args> (_Vals)...)
274 {
275 }
276 template <typename _Uty, typename... _Args>
277 requires is_constructible_v<T, initializer_list<_Uty>&, _Args...>
278 constexpr explicit unexpected (in_place_t, initializer_list<_Uty> _Ilist, _Args&&... _Vals)
279 : _Unexpected (_Ilist, forward<_Args> (_Vals)...)
280 {
281 }
282
283 constexpr const T& error () const& noexcept
284 {
285 return _Unexpected;
286 }
287 constexpr T& error () & noexcept
288 {
289 return _Unexpected;
290 }
291 constexpr const T&& error () const&& noexcept
292 {
293 return std::move (_Unexpected);
294 }
295 constexpr T&& error () && noexcept
296 {
297 return std::move (_Unexpected);
298 }
299
300 constexpr void swap (unexpected& _Other)
301 {
302 using std::swap;
303 swap (_Unexpected, _Other._Unexpected); // intentional ADL
304 }
305
306 friend constexpr void swap (unexpected& _Left, unexpected& _Right)
307 requires is_swappable<T>::value // TRANSITION, /permissive needs ::value
308 {
309 _Left.swap (_Right);
310 }
311
312 template <class _UErr>
313 friend constexpr bool operator== (const unexpected& _Left, const unexpected<_UErr>& _Right)
314 {
315 return _Left._Unexpected == _Right.error ();
316 }
317
318 private:
319 T _Unexpected;
320 };
321 template <typename T>
323
324 /**
325 * Wrap a simplified version of std::expected, cuz handy even if c++23 not present
326 *
327 * \note STILL VERY ROUGH DRAFT - IN USE - BUT NOT SURE ITS RIGHT (cuz mine works but delegating to
328 * MSFT one not working, so maybe mine wrong).
329 */
330 template <typename T, typename E>
331 class expected;
332 template <typename T, typename E>
333 class expected {
334 public:
335 using value_type = T;
336 using error_type = E;
338
339 constexpr expected () noexcept = default;
340 constexpr expected (const expected&) noexcept = default;
341 constexpr expected (T v)
342 : fData_{v}
343 {
344 }
345 constexpr expected (const unexpected_type& e)
346 : fData_{e.error ()}
347 {
348 }
349 template <typename T1, typename E1>
350 constexpr expected (const expected<T1, E1>& e)
351 {
352 if (e) {
353 fData_ = e.value ();
354 }
355 else {
356 fData_ = e.error ();
357 }
358 }
359 explicit operator bool () const noexcept
360 {
361 return std::get_if<T> (&fData_) != nullptr;
362 }
363 T operator* () const
364 {
365 return value ();
366 }
367 T value () const
368 {
369 return get<T> (fData_);
370 }
371 E error () const
372 {
373 return get<E> (fData_);
374 }
375
376 private:
377 variant<T, E> fData_;
378 };
379#endif
380
381 /**
382 * \brief qStroika_ATTRIBUTE_INDETERMINATE is used where you would use a C++ attribute for a variable that is intentionally uninitialized
383 *
384 * [[indeterminate]]
385 * https://en.cppreference.com/w/cpp/language/attributes/indeterminate.html
386 *
387 * \par Example Usage
388 * \code
389 * qStroika_ATTRIBUTE_INDETERMINATE byte r[1024]; // don't initialize explicitly cuz filled in below before used (performance)
390 * \endcode
391 */
392#if __has_cpp_attribute(indeterminate)
393#define qStroika_ATTRIBUTE_INDETERMINATE [[indeterminate]]
394#else
395#define qStroika_ATTRIBUTE_INDETERMINATE
396#endif
397
398 /**
399 * \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.
400 *
401 * The assume attribute was introduced in c++23, and Stroika OPTIONALLY supports this, but doesn't require it as of Stroika v3.
402 * So use qStroika_ATTRIBUTE_ASSUME () to conditionally use [[assume(X)]]
403 */
404#if __has_cpp_attribute(assume)
405#define qStroika_ATTRIBUTE_ASSUME(X) [[assume (X)]];
406#elif _MSC_VER
407 // Docs not clear.
408 // https://www.open-std.org/jtc1/sc22/wg21/docs/papers/2021/p1774r4.pdf suggests this hack. BUT...
409 // 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
410#define qStroika_ATTRIBUTE_ASSUME(X) __assume (X);
411#else
412#define qStroika_ATTRIBUTE_ASSUME(X)
413#endif
414
415#if qCompilerAndStdLib_AssumeWarningSpamming_Buggy
416 // INTENTIONALLY UNBALANCED WITH _END - cuz this is used all over the place!!!
417 DISABLE_COMPILER_CLANG_WARNING_START ("clang diagnostic ignored \"-Wassume\"");
418#endif
419
420 /**
421 * \brief qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS - used for the attribute [[no_unique_address]]
422 *
423 * Though all the compilers we target SOMEWHAT support this, MSVC appears to support it by ignoring it. Which is crazy
424 * cuz it DOES support msvc::no_unique_address
425 */
426#if qCompilerAndStdLib_NO_UNIQUE_ADDR_IgnoredAndMustUseMSVCNOUNIQUE_Buggy && defined(_MSC_VER)
427#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
428#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
429#else
430#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[msvc::no_unique_address]]
431#endif
432#elif __has_cpp_attribute(no_unique_address)
433#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS [[no_unique_address]]
434#else
435#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
436#endif
437
438 /**
439 * \brief [[msvc::no_unique_address]] isn't always broken in MSVC. Annotate with this
440 * on things where its not broken.
441 */
442#if qCompilerAndStdLib_NO_UNIQUE_ADDR_REALLYREALLY_Buggy
443#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE [[msvc::no_unique_address]]
444#else
445#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
446#endif
447
448 /**
449 * \brief qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY same as qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
450 *
451 * EMPHASIZE that this is qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
452 * and not qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCFORCE cuz its known buggy
453 */
454#define qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS_VCBUGGY qStroika_ATTRIBUTE_NO_UNIQUE_ADDRESS
455
456}
457
458/*
459 ********************************************************************************
460 ***************************** Implementation Details ***************************
461 ********************************************************************************
462 */
463#include "StdCompat.inl"
464
465#endif /*_Stroika_Foundation_Common_StdCompat_h_*/
constexpr bool isnan(T v) noexcept
Definition StdCompat.h:223
To bit_cast(const From &src) noexcept
Definition StdCompat.h:161
constexpr bool isinf(T v) noexcept
Definition StdCompat.h:202
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.