Repository: bitcoin/bitcoin
File: src/util/expected.h (C/C++)

Code before:
// Copyright (c) The Bitcoin Core developers
// Distributed under the MIT software license, see the accompanying
// file COPYING or https://opensource.org/license/mit.

#ifndef BITCOIN_UTIL_EXPECTED_H
#define BITCOIN_UTIL_EXPECTED_H

#include <attributes.h>

#include <cassert>
#include <type_traits>
#include <utility>
#include <variant>

namespace util {

/// The util::Unexpected class represents an unexpected value stored in
/// util::Expected.
template <class E>
class Unexpected
{
public:
    constexpr explicit Unexpected(E e) : err(std::move(e)) {}
    E err;
};

/// The util::Expected class provides a standard way for low-level functions to
/// return either error values or result values.
///
/// It provides a smaller version of std::expected from C++23. Missing features
/// can be added, if needed.
template <class T, class E>
class Expected
{
private:
    using ValueType = std::conditional_t<std::is_same_v<T, void>, std::monostate, T>;
    std::variant<ValueType, E> m_data;

public:
    constexpr Expected() : m_data{std::in_place_index_t<0>{}, ValueType{}} {}
    constexpr Expected(ValueType v) : m_data{std::in_place_index_t<0>{}, std::move(v)} {}
    template <class Err>
    constexpr Expected(Unexpected<Err> u) : m_data{std::in_place_index_t<1>{}, std::move(u.err)}
    {
    }

    constexpr bool has_value() const noexcept { return m_data.index() == 0; }
    constexpr explicit operator bool() const noexcept { return has_value(); }

    constexpr const ValueType& value() const LIFETIMEBOUND
    {
        assert(has_value());
        return std::get<0>(m_data);
    }
    constexpr ValueType& value() LIFETIMEBOUND
    {
        assert(has_value());
        return std::get<0>(m_data);
    }

    template <class U>
    ValueType value_or(U&& default_value) const&
    {
        return has_value() ? value() : std::forward<U>(default_value);
    }
    template <class U>
    ValueType value_or(U&& default_value) &&
    {
        return has_value() ? std::move(value()) : std::forward<U>(default_value);
    }

    constexpr const E& error() const LIFETIMEBOUND
    {
        assert(!has_value());
        return std::get<1>(m_data);
    }
    constexpr E& error() LIFETIMEBOUND
    {
        assert(!has_value());
        return std::get<1>(m_data);
    }

    constexpr ValueType& operator*() LIFETIMEBOUND { return value(); }
    constexpr const ValueType& operator*() const LIFETIMEBOUND { return value(); }

    constexpr ValueType* operator->() LIFETIMEBOUND { return &value(); }
    constexpr const ValueType* operator->() const LIFETIMEBOUND { return &value(); }
};

} // namespace util

#endif // BITCOIN_UTIL_EXPECTED_H


Review comment (refactor):
~nit: Could use `optional` instead of `monostate`?~ Ah, I see that was already discussed https://github.com/bitcoin/bitcoin/pull/34032#issuecomment-3632418418.

<details><summary>Diff</summary>

```diff
--- a/src/util/expected.h
+++ b/src/util/expected.h
@@ -10,6 +10,7 @@
 
 #include <cassert>
 #include <exception>
+#include <optional>
 #include <utility>
 #include <variant>
 
@@ -119,16 +120,16 @@ template <class E>
 class Expected<void, E>
 {
 private:
-    std::variant<std::monostate, E> m_data;
+    std::optional<E> m_error;
 
 public:
-    constexpr Expected() : m_data{std::in_place_index<0>, std::monostate{}} {}
+    constexpr Expected() = default;
     template <class Err>
-    constexpr Expected(Unexpected<Err> u) : m_data{std::in_place_index<1>, std::move(u).error()}
+    constexpr Expected(Unexpected<Err> u) : m_error{std::move(u).error()}
     {
     }
 
-    constexpr bool has_value() const noexcept { return m_data.index() == 0; }
+    constexpr bool has_value() const noexcept { return !m_error.has_value(); }
     constexpr explicit operator bool() const noexcept { return has_value(); }
 
     constexpr void value() const
@@ -141,17 +142,17 @@ public:
     constexpr const E& error() const& LIFETIMEBOUND
     {
         assert(!has_value());
-        return std::get<1>(m_data);
+        return m_error.value();
     }
     constexpr E& error() & LIFETIMEBOUND
     {
         assert(!has_value());
-        return std::get<1>(m_data);
+        return m_error.value();
     }
     constexpr E&& error() && LIFETIMEBOUND
     {
         assert(!has_value());
-        return std::get<1>(std::move(m_data));
+        return std::move(m_error).value();
     }
 };

```

</details>


Code after:
    }
    template <class U>
    T value_or(U&& default_value) &&
    {
        return has_value() ? std::move(value()) : std::forward<U>(default_value);
    }

    constexpr const E& error() const& noexcept LIFETIMEBOUND { return *Assert(std::get_if<1>(&m_data)); }
    constexpr E& error() & noexcept LIFETIMEBOUND { return *Assert(std::get_if<1>(&m_data)); }
    constexpr E&& error() && noexcept LIFETIMEBOUND { return std::move(error()); }

    constexpr void swap(Expected& other) noexcept { m_data.swap(other.m_data); }

    constexpr T& operator*() & noexcept LIFETIMEBOUND { return value(); }
    constexpr const T& operator*() const& noexcept LIFETIMEBOUND { return value(); }
    constexpr T&& operator*() && noexcept LIFETIMEBOUND { return std::move(value()); }

    constexpr T* operator->() noexcept LIFETIMEBOUND { return &value(); }
    constexpr const T* operator->() const noexcept LIFETIMEBOUND { return &value(); }
};

template <class E>
class Expected<void, E>
{
private:
    std::variant<std::monostate, E> m_data;

public:
    constexpr Expected() : m_data{std::in_place_index<0>, std::monostate{}} {}
    template <class Err>
    constexpr Expected(Unexpected<Err> u) : m_data{std::in_place_index<1>, std::move(u).error()}
    {
    }

    constexpr bool has_value() const noexcept { return m_data.index() == 0; }
    constexpr explicit operator bool() const noexcept { return has_value(); }

    constexpr void operator*() const noexcept { return value(); }
    constexpr void value() const
    {
        if (!has_value()) {
            throw BadExpectedAccess{};
        }
    }

    constexpr const E& error() const& noexcept LIFETIMEBOUND { return *Assert(std::get_if<1>(&m_data)); }
    constexpr E& error() & noexcept LIFETIMEBOUND { return *Assert(std::get_if<1>(&m_data)); }
    constexpr E&& error() && noexcept LIFETIMEBOUND { return std::move(error()); }
};

} // namespace util