// itlib-expected v1.02 // // A union-type of a value and an error // // SPDX-License-Identifier: MIT // MIT License: // Copyright(c) 2021-2022 Borislav Stanimirov // // Permission is hereby granted, free of charge, to any person obtaining // a copy of this software and associated documentation files(the // "Software"), to deal in the Software without restriction, including // without limitation the rights to use, copy, modify, merge, publish, // distribute, sublicense, and / or sell copies of the Software, and to // permit persons to whom the Software is furnished to do so, subject to // the following conditions : // // The above copyright notice and this permission notice shall be // included in all copies or substantial portions of the Software. // // 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 AND // NONINFRINGEMENT.IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE // LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION // OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION // WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. // // // VERSION HISTORY // // 1.02 (2022-09-02) Specializations for ref and void values and void errors // 1.01 (2021-09-27) Fixed value_or which could return a ref to temporary // 1.00 (2021-09-26) Initial release // // // DOCUMENTATION // // Simply include this file wherever you need. // It defines the class itlib::expected, which is a union type of a value and // an error. It is similar to C++23's std::expected // // It is also somewhat similar to std::optional. In a way you can think of // std::optional as an itlib::expected WITH THE NOTABLE DIFFERENCE // that a default-constructed expected is truthy (it invokes the default // contstructor) of T // // It is also similar to the the proposed std::expected // http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2017/p0323r3.pdf // however it is not the same. itlib::expected does 90% of the job of the // proposed std::expected with 10% of the code. // // Notably itlib::expected is a non-copyable type. It is simply too much effort // to take care of the case where a copy-constructor throws and copying it // seems like a very, very rare need. // // In most cases one would return an itlib::expected from a function and the // lifetime of the object will span only the caller's scope. // // expected defines specializations for ref and void values and void errors. // A void error essentially makes the class equivalent to std::optional, but // again WITH THE NOTABLE DIFFERENCE that default-constructed expected // is truthy. // // Basics // // itlib::expected has two template arguments: // * an "expected" or "value" type T: typedef-ed as value_type // * an "unexpected" or "error" type E: typedef-ed as error_type // // It is a union type: It holds either a value or an error. // // It is a boolean type: It is truthy if it holds a value and falsy if it holds // an error. // // It is an accessor type: It has dereference operators (* and ->) which lead // to the value type inside. Invoking them if itlib::expected holds an error // leads to undefined behavior. // // It is a non-copyable type: Only move ops are allowed, even if the value or // error inside are copyable. // // Ways to construct it with an "expected" type: // * The default constructor. It will in turn invoke the default constructor // of value_type. // * With an existing variable of value_type which can be copied or moved // inside. itlib::expected has a implicit constructor from T&&, so you can // simply return a T from a function which returns itlib::expected. // // Ways to costruct with an "unexpected" type // * Use `unexpected()`. It can be use to initialize itlib::expected with the // default constructor of error_type // * Use `unexpected(E&&)`. It can construct an itlib::expected with an // existing error type variable // // Example // // enum class int_error { division_by_zero, signed_overflow }; // // itlib::expected divide(int a, int b) { // if (b == 0) return unexpected(int_error::division_by_zero); // return a/b; // } // ... // auto res = divide(x, y); // if (!res) cerr << int_error_to_string(res.error()) << '\n'; // else cout << *res << '\n'; // // Reference // // Helpers // * unexpected() - free function used to create an itlib::expected with a // default-constructed error_type // * unexpected(E&&) - free function used to create an itlib::expected with a // error_type from E // * unexpected_t - type which can be used to create an itlib::expected with // any value_type and an error which can be constructed from E // * unexpected_t - type which can be used to create an itlib::expected // with any value_type and any error_type // Main class: expected - union type of a value T and error E // * expected() - constructs with a default-constructed value T // * expected(T&& t) - constructs with a value T from t // * expected(unexpected_t) - constructs with an error // * expected(expected&&) - move ctor // * operator=(expected&&) - move assignment // Boolean interface: // * has_value() - true if it holds a value // * has_error() - true if it holds an error // * operator bool() = has_value() // Get value: // * value() - returns value // * value_or(T t) - returns value if truthy or t if falsy // * operator* - returns value // * operator-> - returns value // Get error: // * error() - return error // // TESTS // // You can find unit tests for expected in its official repo: // https://github.com/iboB/itlib/blob/master/test/ // #pragma once #include #include #include namespace itlib { template class unexpected_t { public: explicit unexpected_t(E&& e) : m_error(std::forward(e)) {} private: template friend class expected; E m_error; }; template unexpected_t unexpected(E&& e) { return unexpected_t(std::forward(e)); } template <> class unexpected_t {}; inline unexpected_t unexpected() noexcept { return {}; } template class expected { public: using value_type = T; using error_type = E; expected() : m_value(), m_has_value(true) {} expected(T&& t) : m_value(std::forward(t)), m_has_value(true) {} template expected(unexpected_t&& u) : m_error(std::move(u.m_error)), m_has_value(false) {} expected(unexpected_t) : m_error(), m_has_value(false) {} // do not copy expected(const expected&) = delete; expected& operator=(const expected&) = delete; // do move expected(expected&& other) noexcept : m_has_value(other.has_value()) { if (m_has_value) { new (&m_value) T(std::move(other.m_value)); } else { new (&m_error) E(std::move(other.m_error)); } } expected& operator=(expected&& other) noexcept { if (m_has_value && other.has_value()) { m_value = std::move(other.m_value); } else if(m_has_value && !other.has_value()) { m_has_value = false; m_value.~T(); ::new (&m_error) E(std::move(other.m_error)); } else if(!m_has_value && other.has_value()) { m_has_value = true; m_error.~E(); ::new (&m_value) T(std::move(other.m_value)); } else { m_error = std::move(other.m_error); } return *this; } ~expected() { if (m_has_value) { m_value.~T(); } else { m_error.~E(); } } // bool interface bool has_value() const { return m_has_value; } bool has_error() const { return !m_has_value; } explicit operator bool() const { return m_has_value; } // value getters T& value() & { assert(has_value()); return m_value; } const T& value() const & { assert(has_value()); return m_value; } T&& value() && { assert(has_value()); return std::move(m_value); } T& operator*() & { return value(); } const T& operator*() const & { return value(); } T&& operator*() && { return std::move(value()); } template T value_or(U&& v) const & { return has_value() ? value() : std::forward(v); } template T value_or(U&& v) && { return has_value() ? std::move(value()) : std::forward(v); } T* operator->() { return &value(); } const T* operator->() const { return &value(); } // error getters E& error() & { assert(has_error()); return m_error; } const E& error() const & { assert(has_error()); return m_error; } E&& error() && { assert(has_error()); return std::move(m_error); } private: union { value_type m_value; error_type m_error; }; bool m_has_value; }; template class expected { public: using value_type = T; using error_type = E; expected(T& t) : m_value(&t) {} template expected(unexpected_t&& u) : m_value(nullptr), m_error(std::move(u.m_error)) {} expected(unexpected_t) : m_value(nullptr), m_error() {} // do not copy expected(const expected&) = delete; expected& operator=(const expected&) = delete; // do move expected(expected&& other) noexcept : m_value(other.m_value) { if (!m_value) { new (&m_error) E(std::move(other.m_error)); } } expected& operator=(expected&& other) noexcept { if (m_value && other.m_value) { m_value = other.m_value; } else if (m_value && !other.m_value) { m_value = nullptr; ::new (&m_error) E(std::move(other.m_error)); } else if (!m_value && other.m_value) { m_value = other.m_value; m_error.~E(); } else { m_error = std::move(other.m_error); } return *this; } ~expected() { if (!m_value) { m_error.~E(); } } // bool interface bool has_value() const { return !!m_value; } bool has_error() const { return !m_value; } explicit operator bool() const { return !!m_value; } // value getters (pointer semantics) T& value() const { assert(has_value()); return *m_value; } T& operator*() const { return value(); } T& value_or(T& v) const { return has_value() ? value() : v; } T* operator->() const { return &value(); } // error getters E& error()& { assert(has_error()); return m_error; } const E& error() const& { assert(has_error()); return m_error; } E&& error()&& { assert(has_error()); return std::move(m_error); } private: value_type* m_value; union { error_type m_error; }; }; template class expected { public: using value_type = void; using error_type = E; expected() : m_has_value(true) {} template expected(unexpected_t&& u) : m_error(std::move(u.m_error)), m_has_value(false) {} expected(unexpected_t) : m_error(), m_has_value(false) {} // do not copy expected(const expected&) = delete; expected& operator=(const expected&) = delete; // do move expected(expected&& other) noexcept : m_has_value(other.has_value()) { if (!m_has_value) { new (&m_error) E(std::move(other.m_error)); } } expected& operator=(expected&& other) noexcept { if (m_has_value && !other.has_value()) { m_has_value = false; ::new (&m_error) E(std::move(other.m_error)); } else if (!m_has_value && other.has_value()) { m_has_value = true; m_error.~E(); } else { m_error = std::move(other.m_error); } return *this; } ~expected() { if (!m_has_value) { m_error.~E(); } } // bool interface bool has_value() const { return m_has_value; } bool has_error() const { return !m_has_value; } explicit operator bool() const { return m_has_value; } // value getters: none // error getters E& error()& { assert(has_error()); return m_error; } const E& error() const& { assert(has_error()); return m_error; } E&& error()&& { assert(has_error()); return std::move(m_error); } private: union { error_type m_error; }; bool m_has_value; }; template class expected { public: using value_type = T; using error_type = void; expected() : m_value(), m_has_value(true) {} expected(T&& t) : m_value(std::forward(t)), m_has_value(true) {} expected(unexpected_t) : m_has_value(false) {} expected(const expected& other) : m_has_value(other.m_has_value) { if (m_has_value) { new (&m_value) T(other.m_value); } } expected& operator=(const expected& other) { if (m_has_value && other.has_value()) { m_value = other.m_value; } else if (m_has_value && !other.has_value()) { m_has_value = false; m_value.~T(); } else if (!m_has_value && other.has_value()) { m_has_value = true; ::new (&m_value) T(other.m_value); } return *this; } expected(expected&& other) noexcept : m_has_value(other.has_value()) { if (m_has_value) { new (&m_value) T(std::move(other.m_value)); } } expected& operator=(expected&& other) noexcept { if (m_has_value && other.has_value()) { m_value = std::move(other.m_value); } else if (m_has_value && !other.has_value()) { m_has_value = false; m_value.~T(); } else if (!m_has_value && other.has_value()) { m_has_value = true; ::new (&m_value) T(std::move(other.m_value)); } return *this; } ~expected() { if (m_has_value) { m_value.~T(); } } // bool interface bool has_value() const { return m_has_value; } bool has_error() const { return !m_has_value; } explicit operator bool() const { return m_has_value; } // optional interface void clear() { if (!m_has_value) return; m_value.~T(); m_has_value = false; } template void emplace(Args&&... args) { clear(); ::new (&m_value) T(std::forward(args)...); m_has_value = true; } // value getters T& value()& { assert(has_value()); return m_value; } const T& value() const& { assert(has_value()); return m_value; } T&& value()&& { assert(has_value()); return std::move(m_value); } T& operator*()& { return value(); } const T& operator*() const& { return value(); } T&& operator*()&& { return std::move(value()); } template T value_or(U&& v) const& { return has_value() ? value() : std::forward(v); } template T value_or(U&& v)&& { return has_value() ? std::move(value()) : std::forward(v); } T* operator->() { return &value(); } const T* operator->() const { return &value(); } // error getters: none private: union { value_type m_value; }; bool m_has_value; }; template class expected { public: using value_type = T; using error_type = void; expected(T& t) : m_value(&t) {} expected(unexpected_t) : m_value(nullptr) {} expected(const expected&) = default; expected& operator=(const expected&) = default; expected(expected&& other) noexcept = default; expected& operator=(expected&& other) noexcept = default; // bool interface bool has_value() const { return !!m_value; } bool has_error() const { return !m_value; } explicit operator bool() const { return !!m_value; } // optional interface void clear() { m_value = nullptr; } void emplace(T& t) { m_value = &t; } // value getters (pointer semantics) T& value() const { assert(has_value()); return *m_value; } T& operator*() const { return value(); } T& value_or(T& v) const { return has_value() ? value() : v; } T* operator->() const { return &value(); } // error getters: none private: value_type* m_value; }; template <> class expected { public: using value_type = void; using error_type = void; expected() : m_has_value(true) {} expected(unexpected_t) : m_has_value(false) {} expected(const expected&) = default; expected& operator=(const expected&) = default; expected(expected&& other) noexcept = default; expected& operator=(expected&& other) noexcept = default; // bool interface bool has_value() const { return m_has_value; } bool has_error() const { return !m_has_value; } explicit operator bool() const { return m_has_value; } // optional interface void clear() { m_has_value = false; } void emplace() { m_has_value = true; } // value getters: none // error getters: none private: bool m_has_value; }; template using eoptional = expected; }