1/* Proposed SG14 status_code
2(C) 2018-2024 Niall Douglas <http://www.nedproductions.biz/> (5 commits)
3File Created: Feb 2018
4
5
6Boost Software License - Version 1.0 - August 17th, 2003
7
8Permission is hereby granted, free of charge, to any person or organization
9obtaining a copy of the software and accompanying documentation covered by
10this license (the "Software") to use, reproduce, display, distribute,
11execute, and transmit the Software, and to prepare derivative works of the
12Software, and to permit third-parties to whom the Software is furnished to
13do so, all subject to the following:
14
15The copyright notices in the Software and this entire statement, including
16the above license grant, this restriction and the following disclaimer,
17must be included in all copies of the Software, in whole or in part, and
18all derivative works of the Software, unless such copies or derivative
19works are solely in the form of machine-executable object code generated by
20a source language processor.
21
22THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
23IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
24FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT
25SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE
26FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE,
27ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER
28DEALINGS IN THE SOFTWARE.
29*/
30
31#ifndef BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP
32#define BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP
33
34#include "config.hpp"
35
36#include <cstring> // for strchr
37
38BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_BEGIN
39
40/*! The main workhorse of the system_error2 library, can be typed (`status_code<DomainType>`), erased-immutable (`status_code<void>`) or erased-mutable
41(`status_code<erased<T>>`).
42
43Be careful of placing these into containers! Equality and inequality operators are
44*semantic* not exact. Therefore two distinct items will test true! To help prevent
45surprise on this, `operator<` and `std::hash<>` are NOT implemented in order to
46trap potential incorrectness. Define your own custom comparison functions for your
47container which perform exact comparisons.
48*/
49template <class DomainType> class status_code;
50class _generic_code_domain;
51//! The generic code is a status code with the generic code domain, which is that of `errc` (POSIX).
52using generic_code = status_code<_generic_code_domain>;
53
54namespace detail
55{
56 template <class StatusCode, class Allocator> class indirecting_domain;
57 /* We are severely limited by needing to retain C++ 11 compatibility when doing
58 constexpr string parsing. MSVC lets you throw exceptions within a constexpr
59 evaluation context when exceptions are globally disabled, but won't let you
60 divide by zero, even if never evaluated, ever in constexpr. GCC and clang won't
61 let you throw exceptions, ever, if exceptions are globally disabled. So let's
62 use the trick of divide by zero in constexpr on GCC and clang if and only if
63 exceptions are globally disabled.
64 */
65#ifdef __GNUC__
66#pragma GCC diagnostic push
67#pragma GCC diagnostic ignored "-Wdiv-by-zero"
68#endif
69#if defined(__cpp_exceptions) || (defined(_MSC_VER) && !defined(__clang__))
70#define BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR(msg) throw msg
71#else
72#define BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR(msg) ((void) msg, 1 / 0)
73#endif
74 constexpr inline unsigned long long parse_hex_byte(char c)
75 {
76 return ('0' <= c && c <= '9') ? (c - '0') :
77 ('a' <= c && c <= 'f') ? (10 + c - 'a') :
78 ('A' <= c && c <= 'F') ? (10 + c - 'A') :
79 BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("Invalid character in UUID");
80 }
81 constexpr inline unsigned long long parse_uuid2(const char *s)
82 {
83 return ((parse_hex_byte(c: s[0]) << 0) | (parse_hex_byte(c: s[1]) << 4) | (parse_hex_byte(c: s[2]) << 8) | (parse_hex_byte(c: s[3]) << 12) |
84 (parse_hex_byte(c: s[4]) << 16) | (parse_hex_byte(c: s[5]) << 20) | (parse_hex_byte(c: s[6]) << 24) | (parse_hex_byte(c: s[7]) << 28) |
85 (parse_hex_byte(c: s[9]) << 32) | (parse_hex_byte(c: s[10]) << 36) | (parse_hex_byte(c: s[11]) << 40) | (parse_hex_byte(c: s[12]) << 44) |
86 (parse_hex_byte(c: s[14]) << 48) | (parse_hex_byte(c: s[15]) << 52) | (parse_hex_byte(c: s[16]) << 56) | (parse_hex_byte(c: s[17]) << 60)) //
87 ^ //
88 ((parse_hex_byte(c: s[19]) << 0) | (parse_hex_byte(c: s[20]) << 4) | (parse_hex_byte(c: s[21]) << 8) | (parse_hex_byte(c: s[22]) << 12) |
89 (parse_hex_byte(c: s[24]) << 16) | (parse_hex_byte(c: s[25]) << 20) | (parse_hex_byte(c: s[26]) << 24) | (parse_hex_byte(c: s[27]) << 28) |
90 (parse_hex_byte(c: s[28]) << 32) | (parse_hex_byte(c: s[29]) << 36) | (parse_hex_byte(c: s[30]) << 40) | (parse_hex_byte(c: s[31]) << 44) |
91 (parse_hex_byte(c: s[32]) << 48) | (parse_hex_byte(c: s[33]) << 52) | (parse_hex_byte(c: s[34]) << 56) | (parse_hex_byte(c: s[35]) << 60));
92 }
93 template <size_t N> constexpr inline unsigned long long parse_uuid_from_array(const char (&uuid)[N])
94 {
95 return (N == 37) ? parse_uuid2(uuid) : ((N == 39) ? parse_uuid2(uuid + 1) : BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("UUID does not have correct length"));
96 }
97 template <size_t N> constexpr inline unsigned long long parse_uuid_from_pointer(const char *uuid)
98 {
99 return (N == 36) ? parse_uuid2(s: uuid) : ((N == 38) ? parse_uuid2(s: uuid + 1) : BOOST_OUTCOME_SYSTEM_ERROR2_FAIL_CONSTEXPR("UUID does not have correct length"));
100 }
101#ifdef __GNUC__
102#pragma GCC diagnostic pop
103#endif
104 static constexpr unsigned long long test_uuid_parse = parse_uuid_from_array(uuid: "430f1201-94fc-06c7-430f-120194fc06c7");
105 // static constexpr unsigned long long test_uuid_parse2 = parse_uuid_from_array("x30f1201-94fc-06c7-430f-120194fc06c7");
106} // namespace detail
107
108/*! Abstract base class for a coding domain of a status code.
109 */
110class status_code_domain
111{
112 template <class DomainType> friend class status_code;
113 template <class StatusCode, class Allocator> friend class detail::indirecting_domain;
114
115public:
116 //! Type of the unique id for this domain.
117 using unique_id_type = unsigned long long;
118 /*! (Potentially thread safe) Reference to a message string.
119
120 Be aware that you cannot add payload to implementations of this class.
121 You get exactly the `void *[3]` array to keep state, this is usually
122 sufficient for a `std::shared_ptr<>` or a `std::string`.
123
124 You can install a handler to be called when this object is copied,
125 moved and destructed. This takes the form of a C function pointer.
126 */
127 class string_ref
128 {
129 public:
130 //! The value type
131 using value_type = const char;
132 //! The size type
133 using size_type = size_t;
134 //! The pointer type
135 using pointer = const char *;
136 //! The const pointer type
137 using const_pointer = const char *;
138 //! The iterator type
139 using iterator = const char *;
140 //! The const iterator type
141 using const_iterator = const char *;
142
143 protected:
144 //! The operation occurring
145 enum class _thunk_op
146 {
147 copy,
148 move,
149 destruct
150 };
151 //! The prototype of the handler function. Copies can throw, moves and destructs cannot.
152 using _thunk_spec = void (*)(string_ref *dest, const string_ref *src, _thunk_op op);
153#ifndef NDEBUG
154 private:
155 static void _checking_string_thunk(string_ref *dest, const string_ref *src, _thunk_op /*unused*/) noexcept
156 {
157 (void) dest;
158 (void) src;
159 assert(dest->_thunk == _checking_string_thunk); // NOLINT
160 assert(src == nullptr || src->_thunk == _checking_string_thunk); // NOLINT
161 // do nothing
162 }
163
164 protected:
165#endif
166 //! Pointers to beginning and end of character range
167 pointer _begin{}, _end{};
168 //! Three `void*` of state
169 void *_state[3]{}; // at least the size of a shared_ptr
170 //! Handler for when operations occur
171 const _thunk_spec _thunk{nullptr};
172
173 constexpr explicit string_ref(_thunk_spec thunk) noexcept
174 : _thunk(thunk)
175 {
176 }
177
178 public:
179 //! Construct from a C string literal
180 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 explicit string_ref(const char *str, size_type len = static_cast<size_type>(-1), void *state0 = nullptr, void *state1 = nullptr,
181 void *state2 = nullptr,
182#ifndef NDEBUG
183 _thunk_spec thunk = _checking_string_thunk
184#else
185 _thunk_spec thunk = nullptr
186#endif
187 ) noexcept
188 : _begin(str)
189 , _end((len == static_cast<size_type>(-1)) ? (str + detail::cstrlen(str)) : (str + len))
190 , // NOLINT
191 _state{state0, state1, state2}
192 , _thunk(thunk)
193 {
194 }
195 //! Copy construct the derived implementation.
196 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 string_ref(const string_ref &o)
197 : _begin(o._begin)
198 , _end(o._end)
199 , _state{o._state[0], o._state[1], o._state[2]}
200 , _thunk(o._thunk)
201 {
202 if(_thunk != nullptr)
203 {
204 _thunk(this, &o, _thunk_op::copy);
205 }
206 }
207 //! Move construct the derived implementation.
208 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 string_ref(string_ref &&o) noexcept
209 : _begin(o._begin)
210 , _end(o._end)
211 , _state{o._state[0], o._state[1], o._state[2]}
212 , _thunk(o._thunk)
213 {
214 if(_thunk != nullptr)
215 {
216 _thunk(this, &o, _thunk_op::move);
217 }
218 }
219 //! Copy assignment
220 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 string_ref &operator=(const string_ref &o)
221 {
222 if(this != &o)
223 {
224#if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND)
225 string_ref temp(static_cast<string_ref &&>(*this));
226 this->~string_ref();
227 try
228 {
229 new(this) string_ref(o); // may throw
230 }
231 catch(...)
232 {
233 new(this) string_ref(static_cast<string_ref &&>(temp));
234 throw;
235 }
236#else
237 this->~string_ref();
238 new(this) string_ref(o);
239#endif
240 }
241 return *this;
242 }
243 //! Move assignment
244 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 string_ref &operator=(string_ref &&o) noexcept
245 {
246 if(this != &o)
247 {
248 this->~string_ref();
249 new(this) string_ref(static_cast<string_ref &&>(o));
250 }
251 return *this;
252 }
253 //! Destruction
254 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 ~string_ref()
255 {
256 if(_thunk != nullptr)
257 {
258 _thunk(this, nullptr, _thunk_op::destruct);
259 }
260 _begin = _end = nullptr;
261 }
262
263 //! Returns whether the reference is empty or not
264 BOOST_OUTCOME_SYSTEM_ERROR2_NODISCARD constexpr bool empty() const noexcept { return _begin == _end; }
265 //! Returns the size of the string
266 constexpr size_type size() const noexcept { return _end - _begin; }
267 //! Returns a null terminated C string
268 constexpr const_pointer c_str() const noexcept { return _begin; }
269 //! Returns a null terminated C string
270 constexpr const_pointer data() const noexcept { return _begin; }
271 //! Returns the beginning of the string
272 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 iterator begin() noexcept { return _begin; }
273 //! Returns the beginning of the string
274 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 const_iterator begin() const noexcept { return _begin; }
275 //! Returns the beginning of the string
276 constexpr const_iterator cbegin() const noexcept { return _begin; }
277 //! Returns the end of the string
278 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 iterator end() noexcept { return _end; }
279 //! Returns the end of the string
280 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 const_iterator end() const noexcept { return _end; }
281 //! Returns the end of the string
282 constexpr const_iterator cend() const noexcept { return _end; }
283 };
284
285 /*! A reference counted, threadsafe reference to a message string.
286 */
287 class atomic_refcounted_string_ref : public string_ref
288 {
289 struct _allocated_msg
290 {
291 mutable std::atomic<unsigned> count{1};
292 };
293 _allocated_msg *&_msg() noexcept { return reinterpret_cast<_allocated_msg *&>(this->_state[0]); } // NOLINT
294 const _allocated_msg *_msg() const noexcept { return reinterpret_cast<const _allocated_msg *>(this->_state[0]); } // NOLINT
295
296 static BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 void _refcounted_string_thunk(string_ref *_dest, const string_ref *_src, _thunk_op op) noexcept
297 {
298 auto dest = static_cast<atomic_refcounted_string_ref *>(_dest); // NOLINT
299 auto src = static_cast<const atomic_refcounted_string_ref *>(_src); // NOLINT
300 (void) src;
301 assert(dest->_thunk == _refcounted_string_thunk); // NOLINT
302 assert(src == nullptr || src->_thunk == _refcounted_string_thunk); // NOLINT
303 switch(op)
304 {
305 case _thunk_op::copy:
306 {
307 if(dest->_msg() != nullptr)
308 {
309 auto count = dest->_msg()->count.fetch_add(i: 1, m: std::memory_order_relaxed);
310 (void) count;
311 assert(count != 0); // NOLINT
312 }
313 return;
314 }
315 case _thunk_op::move:
316 {
317 assert(src); // NOLINT
318 auto msrc = const_cast<atomic_refcounted_string_ref *>(src); // NOLINT
319 msrc->_begin = msrc->_end = nullptr;
320 msrc->_state[0] = msrc->_state[1] = msrc->_state[2] = nullptr;
321 return;
322 }
323 case _thunk_op::destruct:
324 {
325 if(dest->_msg() != nullptr)
326 {
327 auto count = dest->_msg()->count.fetch_sub(i: 1, m: std::memory_order_release);
328 if(count == 1)
329 {
330 std::atomic_thread_fence(m: std::memory_order_acquire);
331 free(ptr: (void *) dest->_begin); // NOLINT
332 delete dest->_msg(); // NOLINT
333 }
334 }
335 }
336 }
337 }
338
339 public:
340 //! Construct from a C string literal allocated using `malloc()`.
341 explicit atomic_refcounted_string_ref(const char *str, size_type len = static_cast<size_type>(-1), void *state1 = nullptr, void *state2 = nullptr) noexcept
342 : string_ref(str, len, new(std::nothrow) _allocated_msg, state1, state2, _refcounted_string_thunk)
343 {
344 if(_msg() == nullptr)
345 {
346 free(ptr: (void *) this->_begin); // NOLINT
347 _msg() = nullptr; // disabled
348 this->_begin = "failed to get message from system";
349 this->_end = strchr(s: this->_begin, c: 0);
350 return;
351 }
352 }
353 };
354
355private:
356 unique_id_type _id;
357
358protected:
359 /*! Use [https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h](https://www.random.org/cgi-bin/randbyte?nbytes=8&format=h) to get a random 64 bit id.
360
361 Do NOT make up your own value. Do NOT use zero.
362 */
363 constexpr explicit status_code_domain(unique_id_type id) noexcept
364 : _id(id)
365 {
366 }
367 /*! UUID constructor, where input is constexpr parsed into a `unique_id_type`.
368 */
369 template <size_t N>
370 constexpr explicit status_code_domain(const char (&uuid)[N]) noexcept
371 : _id(detail::parse_uuid_from_array<N>(uuid))
372 {
373 }
374 template <size_t N> struct _uuid_size
375 {
376 };
377 //! Alternative UUID constructor
378 template <size_t N>
379 constexpr explicit status_code_domain(const char *uuid, _uuid_size<N> /*unused*/) noexcept
380 : _id(detail::parse_uuid_from_pointer<N>(uuid))
381 {
382 }
383 //! No public copying at type erased level
384 status_code_domain(const status_code_domain &) = default;
385 //! No public moving at type erased level
386 status_code_domain(status_code_domain &&) = default;
387 //! No public assignment at type erased level
388 status_code_domain &operator=(const status_code_domain &) = default;
389 //! No public assignment at type erased level
390 status_code_domain &operator=(status_code_domain &&) = default;
391 //! No public destruction at type erased level
392 ~status_code_domain() = default;
393
394public:
395 //! True if the unique ids match.
396 constexpr bool operator==(const status_code_domain &o) const noexcept { return _id == o._id; }
397 //! True if the unique ids do not match.
398 constexpr bool operator!=(const status_code_domain &o) const noexcept { return _id != o._id; }
399 //! True if this unique is lower than the other's unique id.
400 constexpr bool operator<(const status_code_domain &o) const noexcept { return _id < o._id; }
401
402 //! Returns the unique id used to identify identical category instances.
403 constexpr unique_id_type id() const noexcept { return _id; }
404 //! Name of this category.
405 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual string_ref name() const noexcept = 0;
406 //! Information about the payload of the code for this domain
407 struct payload_info_t
408 {
409 size_t payload_size{0}; //!< The payload size in bytes
410 size_t total_size{0}; //!< The total status code size in bytes (includes domain pointer and mixins state)
411 size_t total_alignment{1}; //!< The total status code alignment in bytes
412
413 payload_info_t() = default;
414 constexpr payload_info_t(size_t _payload_size, size_t _total_size, size_t _total_alignment)
415 : payload_size(_payload_size)
416 , total_size(_total_size)
417 , total_alignment(_total_alignment)
418 {
419 }
420 };
421 //! Information about this domain's payload
422 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual payload_info_t payload_info() const noexcept = 0;
423
424protected:
425 //! True if code means failure.
426 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual bool _do_failure(const status_code<void> &code) const noexcept = 0;
427 //! True if code is (potentially non-transitively) equivalent to another code in another domain.
428 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual bool _do_equivalent(const status_code<void> &code1, const status_code<void> &code2) const noexcept = 0;
429 //! Returns the generic code closest to this code, if any.
430 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual generic_code _generic_code(const status_code<void> &code) const noexcept = 0;
431 //! Return a reference to a string textually representing a code.
432 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual string_ref _do_message(const status_code<void> &code) const noexcept = 0;
433#if defined(_CPPUNWIND) || defined(__EXCEPTIONS) || defined(BOOST_OUTCOME_STANDARDESE_IS_IN_THE_HOUSE)
434 //! Throw a code as a C++ exception.
435 BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual void _do_throw_exception(const status_code<void> &code) const = 0;
436#else
437 // Keep a vtable slot for binary compatibility
438 BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN virtual void _do_throw_exception(const status_code<void> & /*code*/) const { abort(); }
439#endif
440 // For a `status_code<erased<T>>` only, copy from `src` to `dst`. Default implementation uses `memcpy()`. You should return false here if your payload is not
441 // trivially copyable or would not fit.
442 virtual bool _do_erased_copy(status_code<void> &dst, const status_code<void> &src, payload_info_t dstinfo) const
443 {
444 // Note that dst may not have its domain set
445 const auto srcinfo = payload_info();
446 if(dstinfo.total_size < srcinfo.total_size)
447 {
448 return false;
449 }
450 const auto tocopy = (dstinfo.total_size > srcinfo.total_size) ? srcinfo.total_size : dstinfo.total_size;
451 memcpy(dest: &dst, src: &src, n: tocopy);
452 return true;
453 } // NOLINT
454 // For a `status_code<erased<T>>` only, destroy the erased value type. Default implementation does nothing.
455 BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR20 virtual void _do_erased_destroy(status_code<void> &code, payload_info_t info) const noexcept // NOLINT
456 {
457 (void) code;
458 (void) info;
459 }
460};
461
462BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_END
463
464#endif
465

source code of boost/libs/outcome/include/boost/outcome/experimental/status-code/status_code_domain.hpp