/* Copyright (c) 2018 Christian Mollekopf This library is free software; you can redistribute it and/or modify it under the terms of the GNU Library General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Library General Public License along with this library; see the file COPYING.LIB. If not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #pragma once #include #include #include #include // A somewhat implementation of the expected monad, proposed here: // https://isocpp.org/files/papers/n4015.pdf // A class used to differentiate errors and values when they are of the same type. template class Unexpected { static_assert(!std::is_same::value, "Cannot have an Unexpected void"); public: Unexpected() = delete; constexpr explicit Unexpected(const Error &error) : mValue(error) {} constexpr explicit Unexpected(Error &&error) : mValue(std::move(error)) {} // For implicit conversions when doing makeUnexpected(other) template constexpr explicit Unexpected(const Unexpected &error) : mValue(error.value()) { } template constexpr explicit Unexpected(Unexpected &&error) : mValue(std::move(error.value())) { } constexpr const Error &value() const & { return mValue; } Error &value() & { return mValue; } constexpr const Error &&value() const && { return std::move(mValue); } Error &&value() && { return std::move(mValue); } private: Error mValue; }; template Unexpected::type> makeUnexpected(Error &&e) { return Unexpected::type>(std::forward(e)); } template bool operator==(const Unexpected &lhs, const Unexpected &rhs) { return lhs.value() == rhs.value(); } template bool operator!=(const Unexpected &lhs, const Unexpected &rhs) { return lhs.value() != rhs.value(); } namespace detail { namespace tags { struct Expected {}; struct Unexpected {}; } // namespace tags // Write functions here when storage related and when Type != void template struct StorageBase { protected: // Rule of 5 {{{ StorageBase(const StorageBase &other) : mIsValue(other.mIsValue) { // This is a constructor, you have to construct object, not assign them // (hence the placement new) // // Here's the problem: // // Object that are part of a union are not initialized (which is // normal). If we replaced the placement new by a line like this: // // ``` // mValue = other.mValue; // ``` // // If overloaded, this will call `mValue.operator=(other.mValue);`, but // since we're in the constructor, mValue is not initialized. This can // cause big issues if the Type is not trivially (move) assignable. if (mIsValue) { new (std::addressof(mValue)) Type(other.mValue); } else { new (std::addressof(mError)) Unexpected(other.mError); } } StorageBase(StorageBase &&other) : mIsValue(other.mIsValue) { // If you're thinking WTF, see the comment in the copy constructor above. if (mIsValue) { new (std::addressof(mValue)) Type(std::move(other.mValue)); } else { new (std::addressof(mError)) Unexpected(std::move(other.mError)); } } constexpr StorageBase &operator=(const StorageBase &other) { mIsValue = other.mIsValue; if (mIsValue) { mValue = other.mValue; } else { mError = other.mError; } return *this; } constexpr StorageBase &operator=(StorageBase &&other) { this->~StorageBase(); mIsValue = other.mIsValue; if (mIsValue) { mValue = std::move(other.mValue); } else { mError = std::move(other.mError); } return *this; } ~StorageBase() { if (mIsValue) { mValue.~Type(); } else { mError.~Unexpected(); } } // }}} template constexpr StorageBase(tags::Expected, Args &&... args) : mValue(std::forward(args)...), mIsValue(true) { } template constexpr StorageBase(tags::Unexpected, Args &&... args) : mError(std::forward(args)...), mIsValue(false) { } union { Unexpected mError; Type mValue; }; bool mIsValue; }; // Write functions here when storage related and when Type == void template struct StorageBase { protected: constexpr StorageBase(tags::Expected) : mIsValue(true) {} template constexpr StorageBase(tags::Unexpected, Args &&... args) : mError(std::forward(args)...), mIsValue(false) { } Unexpected mError; bool mIsValue; }; // Write functions here when storage related, whether Type is void or not template struct Storage : StorageBase { protected: // Forward the construction to StorageBase using StorageBase::StorageBase; }; // Write functions here when dev API related and when Type != void template struct ExpectedBase : detail::Storage { constexpr ExpectedBase() : detail::Storage(detail::tags::Expected{}) {} template constexpr ExpectedBase(const Unexpected &error) : detail::Storage(detail::tags::Unexpected{}, error) { } template constexpr ExpectedBase(Unexpected &&error) : detail::Storage(detail::tags::Unexpected{}, std::move(error)) { } constexpr ExpectedBase(const Type &value) : detail::Storage(detail::tags::Expected{}, value) { } constexpr ExpectedBase(Type &&value) : detail::Storage(detail::tags::Expected{}, std::move(value)) { } // Warning: will crash if this is an error. You should always check this is // an expected value before calling `.value()` constexpr const Type &value() const & { Q_ASSERT(this->mIsValue); return this->mValue; } Type &&value() && { Q_ASSERT(this->mIsValue); return std::move(this->mValue); } }; // Write functions here when dev API related and when Type == void template struct ExpectedBase : detail::Storage { // Rewrite constructors for unexpected because Expected doesn't have direct access to it. template constexpr ExpectedBase(const Unexpected &error) : detail::Storage(detail::tags::Unexpected{}, error) { } template constexpr ExpectedBase(Unexpected &&error) : detail::Storage(detail::tags::Unexpected{}, std::move(error)) { } }; } // namespace detail // Write functions here when dev API related, whether Type is void or not template class Expected : public detail::ExpectedBase { static_assert(!std::is_same::value, "Expected with void Error is not implemented"); public: using detail::ExpectedBase::ExpectedBase; constexpr const Error &error() const & { return this->mError.value(); } constexpr bool isValue() const { return this->mIsValue; } constexpr explicit operator bool() const { return this->mIsValue; } };