/* * libfud * Copyright 2024 Dominick Allen * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #ifndef FUD_RESULT_HPP #define FUD_RESULT_HPP #include "fud_assert.hpp" #include "fud_option.hpp" #include "fud_status.hpp" #include #include // IWYU pragma: keep (placement new) #include #include namespace fud { template struct Okay { T value; }; template struct Error { E value; }; using FudError = Error; /** \brief A result type which contains either a T on success or an E on error. */ template class [[nodiscard]] Result { public: using ResultType = Result; constexpr ~Result() noexcept { destroy(); } constexpr Result(const Okay& value) : m_data{}, m_discriminant{Discriminant::Okay} { auto ptrValue = new (m_data.data()) T(value.value); fudAssert(ptrValue != nullptr); } constexpr Result(Okay&& value) : m_data{}, m_discriminant{Discriminant::Okay} { auto ptrValue = new (m_data.data()) T(std::move(value.value)); fudAssert(ptrValue != nullptr); } constexpr Result(const Error& value) : m_data{}, m_discriminant{Discriminant::Error} { auto ptrValue = new (m_data.data()) E(value.value); fudAssert(ptrValue != nullptr); } constexpr Result(E&& value) : m_data{}, m_discriminant{Discriminant::Error} { auto ptrValue = new (m_data.data()) E(std::move(value.value)); fudAssert(ptrValue != nullptr); } constexpr Result(const Result& rhs) : m_data{}, m_discriminant{rhs.m_discriminant} { fudAssert(m_discriminant != Discriminant::Invalid); if (isOkay()) { auto ptrValue = new (m_data.data()) T(rhs.getOkay()); fudAssert(ptrValue != nullptr); } else { auto ptrValue = new (m_data.data()) E(rhs.getError()); fudAssert(ptrValue != nullptr); } } constexpr Result(Result&& rhs) : m_data{}, m_discriminant{rhs.m_discriminant} { fudAssert(m_discriminant != Discriminant::Invalid); if (isOkay()) { auto ptrValue = new (m_data.data()) T(rhs.takeOkay()); fudAssert(ptrValue != nullptr); } else { auto ptrValue = new (m_data.data()) E(rhs.takeError()); fudAssert(ptrValue != nullptr); } rhs.m_discriminant = Discriminant::Invalid; } constexpr Result& operator=(const Result& rhs) { if (&rhs == this) { return *this; } destroy(); m_discriminant = rhs.m_discriminant; fudAssert(m_discriminant != Discriminant::Invalid); if (isOkay()) { auto ptrValue = new (m_data.data()) T(rhs.getOkay()); fudAssert(ptrValue != nullptr); } else { auto ptrValue = new (m_data.data()) E(rhs.getError()); fudAssert(ptrValue != nullptr); } return *this; } constexpr Result& operator=(Result&& rhs) { if (&rhs == this) { return *this; } destroy(); m_discriminant = rhs.m_discriminant; fudAssert(m_discriminant != Discriminant::Invalid); if (isOkay()) { auto ptrValue = new (m_data.data()) T(rhs.takeOkay()); fudAssert(ptrValue != nullptr); } else { auto ptrValue = new (m_data.data()) E(rhs.takeError()); fudAssert(ptrValue != nullptr); } return *this; } static constexpr ResultType okay(const T& okay) { return ResultType{okay}; } static constexpr ResultType okay(T&& okay) { return ResultType{std::move(okay)}; } static constexpr ResultType error(const E& error) { return ResultType{error}; } static constexpr ResultType error(E&& error) { return ResultType{std::move(error)}; } template static constexpr ResultType okay(const Result& okayRes) { return ResultType::okay(okayRes.getOkay()); } template static constexpr ResultType okay(Result&& okayRes) { return ResultType::okay(okayRes.takeOkay()); } template static constexpr ResultType error(const Result& errorRes) { return ResultType::error(errorRes.getError()); } template static constexpr ResultType error(Result&& errorRes) { return ResultType::error(errorRes.takeError()); } [[nodiscard]] constexpr bool isOkay() const { fudAssert(m_discriminant != Discriminant::Invalid); return (m_discriminant == Discriminant::Okay); } [[nodiscard]] constexpr bool isError() const { fudAssert(m_discriminant != Discriminant::Invalid); return (m_discriminant == Discriminant::Error); } [[nodiscard]] constexpr const T& getOkay() const& { fudAssert(isOkay()); return *reinterpret_cast(m_data.data()); } [[nodiscard]] constexpr const T& getOkayOr(const T& alternative) const& { if (!isOkay()) { return alternative; } return *reinterpret_cast(m_data.data()); } [[nodiscard]] constexpr const E& getError() const& { fudAssert(isError()); return *reinterpret_cast(m_data.data()); } [[nodiscard]] constexpr const E& getErrorOr(const E& alternative) const& { if (!isError()) { return alternative; } return *reinterpret_cast(m_data.data()); } [[nodiscard]] constexpr T&& takeOkay() { fudAssert(isOkay()); return std::move(*reinterpret_cast(m_data.data())); } [[nodiscard]] constexpr T&& takeOkayOr(T&& alternative) { if (!isOkay()) { return std::move(alternative); } return std::move(*reinterpret_cast(m_data.data())); } [[nodiscard]] constexpr E&& takeError() { fudAssert(isError()); return std::move(*reinterpret_cast(m_data.data())); } [[nodiscard]] constexpr E&& takeErrorOr(E&& alternative) { if (!isError()) { return std::move(alternative); } return std::move(*reinterpret_cast(m_data.data())); } private: constexpr Result() requires(std::is_default_constructible_v) : m_data{}, m_discriminant{Discriminant::Okay} { auto ptrValue = new (m_data.data()) T(); fudAssert(ptrValue != nullptr); } constexpr Result(const T& value) requires(not std::is_convertible_v and not std::is_convertible_v) : m_data{}, m_discriminant{Discriminant::Okay} { auto ptrValue = new (m_data.data()) T(value); fudAssert(ptrValue != nullptr); } constexpr Result(T&& value) requires(not std::is_convertible_v and not std::is_convertible_v) : m_data{}, m_discriminant{Discriminant::Okay} { auto ptrValue = new (m_data.data()) T(std::move(value)); fudAssert(ptrValue != nullptr); } constexpr Result(const E& value) requires(not std::is_convertible_v and not std::is_convertible_v) : m_data{}, m_discriminant{Discriminant::Error} { auto ptrValue = new (m_data.data()) E(value); fudAssert(ptrValue != nullptr); } constexpr Result(E&& value) requires(not std::is_convertible_v and not std::is_convertible_v) : m_data{}, m_discriminant{Discriminant::Error} { auto ptrValue = new (m_data.data()) E(std::move(value)); fudAssert(ptrValue != nullptr); } constexpr void destroy() noexcept { if (m_discriminant == Discriminant::Okay) { reinterpret_cast(m_data.data())->~T(); m_discriminant = Discriminant::Invalid; } else if (m_discriminant == Discriminant::Error) { reinterpret_cast(m_data.data())->~E(); m_discriminant = Discriminant::Invalid; } } static constexpr auto Size = std::max(sizeof(T), sizeof(E)); static constexpr auto Align = std::max(alignof(T), alignof(E)); alignas(Align) option_detail::DataArray m_data{}; enum class Discriminant : uint8_t { Invalid, Okay, Error, } m_discriminant{Discriminant::Invalid}; }; #define M_TakeOrReturn(HYGIENE_RESULT_TYPE, HYGIENE_EXPRESSION) \ ({ \ auto HYGIENE_RESULT{(HYGIENE_EXPRESSION)}; \ if (HYGIENE_RESULT.isError()) { \ return HYGIENE_RESULT_TYPE::error(HYGIENE_RESULT.takeError()); \ } \ HYGIENE_RESULT.takeOkay(); \ }) } // namespace fud #endif