status_code_domain.hpp 19 KB

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