/* * 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_OPTION_HPP #define FUD_OPTION_HPP #include "fud_assert.hpp" #include #include #include // IWYU pragma: keep (placement new) #include namespace fud { namespace option_detail { struct NullOptionType { enum class NullOptConstructor : char { Monostate }; constexpr explicit NullOptionType(NullOptConstructor /*unnamed*/) { } }; template struct DataArray { // NOLINTBEGIN(cppcoreguidelines-avoid-c-arrays) std::byte m_data[Size]; // NOLINTEND(cppcoreguidelines-avoid-c-arrays) constexpr std::byte* data() noexcept { return m_data; } [[nodiscard]] constexpr const std::byte* data() const noexcept { return m_data; } constexpr bool operator==(const DataArray&) const noexcept = default; constexpr void clear() { for (size_t idx = 0; idx < Size; ++idx) { m_data[idx] = std::byte(0); } } }; } // namespace option_detail inline constexpr option_detail::NullOptionType NullOpt{option_detail::NullOptionType::NullOptConstructor::Monostate}; template class Option { private: static_assert(!std::is_same_v); static constexpr bool IsRef = std::is_reference_v; public: using ValueType = std::remove_reference_t; static constexpr size_t Size = IsRef ? sizeof(std::reference_wrapper) : sizeof(ValueType); constexpr Option() noexcept : m_engaged{false} { } constexpr Option(option_detail::NullOptionType nullOpt) noexcept : m_engaged{false} { static_cast(nullOpt); } constexpr Option(T value) noexcept : m_engaged{true} { if constexpr (IsRef) { new (m_data.data()) std::reference_wrapper(std::ref(value)); } else { new (m_data.data()) ValueType(value); } } constexpr static Option take(T&& value) noexcept { Option option{}; option.m_engaged = true; if constexpr (IsRef) { new (option.m_data.data()) std::reference_wrapper(std::ref(value)); } else { new (option.m_data.data()) ValueType(std::move(value)); } return option; } constexpr Option(const Option& rhs) noexcept : m_data(rhs.m_data), m_engaged(rhs.m_engaged) { } constexpr Option(Option&& rhs) noexcept : m_data(std::move(rhs.m_data)), m_engaged(rhs.m_engaged) { rhs.cleanup(); } constexpr ~Option() noexcept { destroy(); } Option& operator=(const Option& rhs) noexcept { if (&rhs == this) { return *this; } destroy(); m_engaged = rhs.m_engaged; m_data = rhs.m_data; return *this; } Option& operator=(Option&& rhs) noexcept { destroy(); m_engaged = rhs.m_engaged; m_data = std::move(rhs.m_data); rhs.cleanup(); return *this; } [[nodiscard]] bool hasValue() const { return m_engaged; } [[nodiscard]] bool isNone() const { return !m_engaged; } operator bool() const { return hasValue(); } [[nodiscard]] constexpr const ValueType& value() const& { fudAssert(m_engaged); if constexpr (IsRef) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast*>(m_data.data()); } else { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast(m_data.data()); } } [[nodiscard]] constexpr ValueType& value() & { fudAssert(m_engaged); if constexpr (IsRef) { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast*>(m_data.data()); } else { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return *reinterpret_cast(m_data.data()); } } [[nodiscard]] constexpr const ValueType&& value() const&& { fudAssert(m_engaged); static_assert(!IsRef); // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) return std::move(*reinterpret_cast(m_data.data())); } [[nodiscard]] constexpr const ValueType& valueOr(const ValueType& alternative) const& { if (m_engaged) { return value(); } return alternative; } [[nodiscard]] constexpr ValueType&& valueOr(ValueType&& alternative) const&& { if (m_engaged) { return value(); } return std::move(alternative); } template constexpr auto map(F&& func) const& -> Option(func)(value()))> { using U = decltype(std::forward(func)(value())); // static_assert(std::is_same_v(func)(value())), Option>()); if (hasValue()) { return Option{std::forward(func)(value())}; } return Option{NullOpt}; } private: constexpr void destroy() noexcept { if (m_engaged) { if constexpr (IsRef) { // reinterpret_cast*>(m_data.data()); } else { // NOLINTNEXTLINE(cppcoreguidelines-pro-type-reinterpret-cast) reinterpret_cast(m_data.data())->~ValueType(); } cleanup(); } } constexpr void cleanup() noexcept { m_engaged = false; m_data.clear(); } static constexpr auto Align = std::max(alignof(ValueType), alignof(std::reference_wrapper)); alignas(Align) option_detail::DataArray m_data{}; bool m_engaged; }; } // namespace fud #endif