/* Proposed SG14 status_code (C) 2018-2019 Niall Douglas (5 commits) File Created: Feb 2018 Boost Software License - Version 1.0 - August 17th, 2003 Permission is hereby granted, free of charge, to any person or organization obtaining a copy of the software and accompanying documentation covered by this license (the "Software") to use, reproduce, display, distribute, execute, and transmit the Software, and to prepare derivative works of the Software, and to permit third-parties to whom the Software is furnished to do so, all subject to the following: The copyright notices in the Software and this entire statement, including the above license grant, this restriction and the following disclaimer, must be included in all copies of the Software, in whole or in part, and all derivative works of the Software, unless such copies or derivative works are solely in the form of machine-executable object code generated by a source language processor. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR ANYONE DISTRIBUTING THE SOFTWARE BE LIABLE FOR ANY DAMAGES OR OTHER LIABILITY, WHETHER IN CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ #ifndef BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP #define BOOST_OUTCOME_SYSTEM_ERROR2_STATUS_CODE_DOMAIN_HPP #include "config.hpp" #include // for strchr BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_BEGIN /*! The main workhorse of the system_error2 library, can be typed (`status_code`), erased-immutable (`status_code`) or erased-mutable (`status_code>`). Be careful of placing these into containers! Equality and inequality operators are *semantic* not exact. Therefore two distinct items will test true! To help prevent surprise on this, `operator<` and `std::hash<>` are NOT implemented in order to trap potential incorrectness. Define your own custom comparison functions for your container which perform exact comparisons. */ template class status_code; class _generic_code_domain; //! The generic code is a status code with the generic code domain, which is that of `errc` (POSIX). using generic_code = status_code<_generic_code_domain>; namespace detail { template class indirecting_domain; template struct status_code_sizer { void *a; T b; }; template struct type_erasure_is_safe { static constexpr bool value = traits::is_move_relocating::value // && (sizeof(status_code_sizer) <= sizeof(status_code_sizer)); }; } // namespace detail /*! Abstract base class for a coding domain of a status code. */ class status_code_domain { template friend class status_code; template friend class indirecting_domain; public: //! Type of the unique id for this domain. using unique_id_type = unsigned long long; /*! (Potentially thread safe) Reference to a message string. Be aware that you cannot add payload to implementations of this class. You get exactly the `void *[3]` array to keep state, this is usually sufficient for a `std::shared_ptr<>` or a `std::string`. You can install a handler to be called when this object is copied, moved and destructed. This takes the form of a C function pointer. */ class string_ref { public: //! The value type using value_type = const char; //! The size type using size_type = size_t; //! The pointer type using pointer = const char *; //! The const pointer type using const_pointer = const char *; //! The iterator type using iterator = const char *; //! The const iterator type using const_iterator = const char *; protected: //! The operation occurring enum class _thunk_op { copy, move, destruct }; //! The prototype of the handler function. Copies can throw, moves and destructs cannot. using _thunk_spec = void (*)(string_ref *dest, const string_ref *src, _thunk_op op); #ifndef NDEBUG private: static void _checking_string_thunk(string_ref *dest, const string_ref *src, _thunk_op /*unused*/) noexcept { (void) dest; (void) src; assert(dest->_thunk == _checking_string_thunk); // NOLINT assert(src == nullptr || src->_thunk == _checking_string_thunk); // NOLINT // do nothing } protected: #endif //! Pointers to beginning and end of character range pointer _begin{}, _end{}; //! Three `void*` of state void *_state[3]{}; // at least the size of a shared_ptr //! Handler for when operations occur const _thunk_spec _thunk{nullptr}; constexpr explicit string_ref(_thunk_spec thunk) noexcept : _thunk(thunk) {} public: //! Construct from a C string literal BOOST_OUTCOME_SYSTEM_ERROR2_CONSTEXPR14 explicit string_ref(const char *str, size_type len = static_cast(-1), void *state0 = nullptr, void *state1 = nullptr, void *state2 = nullptr, #ifndef NDEBUG _thunk_spec thunk = _checking_string_thunk #else _thunk_spec thunk = nullptr #endif ) noexcept : _begin(str), _end((len == static_cast(-1)) ? (str + detail::cstrlen(str)) : (str + len)), // NOLINT _state{state0, state1, state2}, _thunk(thunk) { } //! Copy construct the derived implementation. string_ref(const string_ref &o) : _begin(o._begin) , _end(o._end) , _state{o._state[0], o._state[1], o._state[2]} , _thunk(o._thunk) { if(_thunk != nullptr) { _thunk(this, &o, _thunk_op::copy); } } //! Move construct the derived implementation. string_ref(string_ref &&o) noexcept : _begin(o._begin), _end(o._end), _state{o._state[0], o._state[1], o._state[2]}, _thunk(o._thunk) { if(_thunk != nullptr) { _thunk(this, &o, _thunk_op::move); } } //! Copy assignment string_ref &operator=(const string_ref &o) { if(this != &o) { #if defined(__cpp_exceptions) || defined(__EXCEPTIONS) || defined(_CPPUNWIND) string_ref temp(static_cast(*this)); this->~string_ref(); try { new(this) string_ref(o); // may throw } catch(...) { new(this) string_ref(static_cast(temp)); throw; } #else this->~string_ref(); new(this) string_ref(o); #endif } return *this; } //! Move assignment string_ref &operator=(string_ref &&o) noexcept { if(this != &o) { this->~string_ref(); new(this) string_ref(static_cast(o)); } return *this; } //! Destruction ~string_ref() { if(_thunk != nullptr) { _thunk(this, nullptr, _thunk_op::destruct); } _begin = _end = nullptr; } //! Returns whether the reference is empty or not BOOST_OUTCOME_SYSTEM_ERROR2_NODISCARD bool empty() const noexcept { return _begin == _end; } //! Returns the size of the string size_type size() const noexcept { return _end - _begin; } //! Returns a null terminated C string const_pointer c_str() const noexcept { return _begin; } //! Returns a null terminated C string const_pointer data() const noexcept { return _begin; } //! Returns the beginning of the string iterator begin() noexcept { return _begin; } //! Returns the beginning of the string const_iterator begin() const noexcept { return _begin; } //! Returns the beginning of the string const_iterator cbegin() const noexcept { return _begin; } //! Returns the end of the string iterator end() noexcept { return _end; } //! Returns the end of the string const_iterator end() const noexcept { return _end; } //! Returns the end of the string const_iterator cend() const noexcept { return _end; } }; /*! A reference counted, threadsafe reference to a message string. */ class atomic_refcounted_string_ref : public string_ref { struct _allocated_msg { mutable std::atomic count{1}; }; _allocated_msg *&_msg() noexcept { return reinterpret_cast<_allocated_msg *&>(this->_state[0]); } // NOLINT const _allocated_msg *_msg() const noexcept { return reinterpret_cast(this->_state[0]); } // NOLINT static void _refcounted_string_thunk(string_ref *_dest, const string_ref *_src, _thunk_op op) noexcept { auto dest = static_cast(_dest); // NOLINT auto src = static_cast(_src); // NOLINT (void) src; assert(dest->_thunk == _refcounted_string_thunk); // NOLINT assert(src == nullptr || src->_thunk == _refcounted_string_thunk); // NOLINT switch(op) { case _thunk_op::copy: { if(dest->_msg() != nullptr) { auto count = dest->_msg()->count.fetch_add(1, std::memory_order_relaxed); (void) count; assert(count != 0); // NOLINT } return; } case _thunk_op::move: { assert(src); // NOLINT auto msrc = const_cast(src); // NOLINT msrc->_begin = msrc->_end = nullptr; msrc->_state[0] = msrc->_state[1] = msrc->_state[2] = nullptr; return; } case _thunk_op::destruct: { if(dest->_msg() != nullptr) { auto count = dest->_msg()->count.fetch_sub(1, std::memory_order_release); if(count == 1) { std::atomic_thread_fence(std::memory_order_acquire); free((void *) dest->_begin); // NOLINT delete dest->_msg(); // NOLINT } } } } } public: //! Construct from a C string literal allocated using `malloc()`. explicit atomic_refcounted_string_ref(const char *str, size_type len = static_cast(-1), void *state1 = nullptr, void *state2 = nullptr) noexcept : string_ref(str, len, new(std::nothrow) _allocated_msg, state1, state2, _refcounted_string_thunk) { if(_msg() == nullptr) { free((void *) this->_begin); // NOLINT _msg() = nullptr; // disabled this->_begin = "failed to get message from system"; this->_end = strchr(this->_begin, 0); return; } } }; private: unique_id_type _id; protected: /*! 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. Do NOT make up your own value. Do NOT use zero. */ constexpr explicit status_code_domain(unique_id_type id) noexcept : _id(id) {} //! No public copying at type erased level status_code_domain(const status_code_domain &) = default; //! No public moving at type erased level status_code_domain(status_code_domain &&) = default; //! No public assignment at type erased level status_code_domain &operator=(const status_code_domain &) = default; //! No public assignment at type erased level status_code_domain &operator=(status_code_domain &&) = default; //! No public destruction at type erased level ~status_code_domain() = default; public: //! True if the unique ids match. constexpr bool operator==(const status_code_domain &o) const noexcept { return _id == o._id; } //! True if the unique ids do not match. constexpr bool operator!=(const status_code_domain &o) const noexcept { return _id != o._id; } //! True if this unique is lower than the other's unique id. constexpr bool operator<(const status_code_domain &o) const noexcept { return _id < o._id; } //! Returns the unique id used to identify identical category instances. constexpr unique_id_type id() const noexcept { return _id; } //! Name of this category. virtual string_ref name() const noexcept = 0; protected: //! True if code means failure. virtual bool _do_failure(const status_code &code) const noexcept = 0; //! True if code is (potentially non-transitively) equivalent to another code in another domain. virtual bool _do_equivalent(const status_code &code1, const status_code &code2) const noexcept = 0; //! Returns the generic code closest to this code, if any. virtual generic_code _generic_code(const status_code &code) const noexcept = 0; //! Return a reference to a string textually representing a code. virtual string_ref _do_message(const status_code &code) const noexcept = 0; #if defined(_CPPUNWIND) || defined(__EXCEPTIONS) || defined(BOOST_OUTCOME_STANDARDESE_IS_IN_THE_HOUSE) //! Throw a code as a C++ exception. BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN virtual void _do_throw_exception(const status_code &code) const = 0; #else // Keep a vtable slot for binary compatibility BOOST_OUTCOME_SYSTEM_ERROR2_NORETURN virtual void _do_throw_exception(const status_code & /*code*/) const { abort(); } #endif // For a `status_code>` only, copy from `src` to `dst`. Default implementation uses `memcpy()`. virtual void _do_erased_copy(status_code &dst, const status_code &src, size_t bytes) const { memcpy(&dst, &src, bytes); } // NOLINT // For a `status_code>` only, destroy the erased value type. Default implementation does nothing. virtual void _do_erased_destroy(status_code &code, size_t bytes) const noexcept // NOLINT { (void) code; (void) bytes; } }; BOOST_OUTCOME_SYSTEM_ERROR2_NAMESPACE_END #endif