util: Add Expected<void, E> specialization
What changed, and why it matters
This is a small internal code cleanup in Bitcoin Core's utility library. It adds a dedicated specialization for 'Expected<void, E>' so that when no return value is needed, the code no longer exposes a placeholder type (std::monostate) through the public value() method. It also adds a couple of unit tests. There is no security fix, behavior change, or externally visible vulnerability addressed.
No security action required. Treat as normal code-quality/refactoring commit.
Security signals we found
No strong security signals were identified.
Evidence from the diff
The commit refactors src/util/expected.h to provide a separate Expected
Changed components
src/util/expected.hsrc/test/util_expected_tests.cppInspect captured patch +44 / −14
diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp
index 64f543bf..e626a988 100644
--- a/src/test/util_expected_tests.cpp
+++ b/src/test/util_expected_tests.cpp
@@ -66,6 +66,8 @@ BOOST_AUTO_TEST_CASE(expected_error)
{
Expected<void, std::string> e{};
BOOST_CHECK(e.has_value());
+ [&]() -> void { return e.value(); }(); // check value returns void and does not throw
+ [&]() -> void { return *e; }();
e = Unexpected{"fail"};
BOOST_CHECK(!e.has_value());
diff --git a/src/util/expected.h b/src/util/expected.h
index d4611698..a7d02c39 100644
--- a/src/util/expected.h
+++ b/src/util/expected.h
@@ -6,10 +6,10 @@
#define BITCOIN_UTIL_EXPECTED_H
#include <attributes.h>
+#include <util/check.h>
#include <cassert>
#include <exception>
-#include <type_traits>
#include <utility>
#include <variant>
@@ -44,28 +44,27 @@ 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;
+ std::variant<T, 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)} {}
+ constexpr Expected() : m_data{std::in_place_index<0>, T{}} {}
+ constexpr Expected(T v) : m_data{std::in_place_index<0>, std::move(v)} {}
template <class Err>
- constexpr Expected(Unexpected<Err> u) : m_data{std::in_place_index_t<1>{}, std::move(u).error()}
+ 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 const ValueType& value() const LIFETIMEBOUND
+ constexpr const T& value() const LIFETIMEBOUND
{
if (!has_value()) {
throw BadExpectedAccess{};
}
return std::get<0>(m_data);
}
- constexpr ValueType& value() LIFETIMEBOUND
+ constexpr T& value() LIFETIMEBOUND
{
if (!has_value()) {
throw BadExpectedAccess{};
@@ -74,12 +73,12 @@ public:
}
template <class U>
- ValueType value_or(U&& default_value) const&
+ T value_or(U&& default_value) const&
{
return has_value() ? value() : std::forward<U>(default_value);
}
template <class U>
- ValueType value_or(U&& default_value) &&
+ T value_or(U&& default_value) &&
{
return has_value() ? std::move(value()) : std::forward<U>(default_value);
}
@@ -95,11 +94,40 @@ public:
return std::get<1>(m_data);
}
- constexpr ValueType& operator*() noexcept LIFETIMEBOUND { return value(); }
- constexpr const ValueType& operator*() const noexcept LIFETIMEBOUND { return value(); }
+ constexpr T& operator*() noexcept LIFETIMEBOUND { return value(); }
+ constexpr const T& operator*() const noexcept LIFETIMEBOUND { return value(); }
- constexpr ValueType* operator->() noexcept LIFETIMEBOUND { return &value(); }
- constexpr const ValueType* operator->() const noexcept LIFETIMEBOUND { return &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
Why this scored 15/100
Community notes
Notes can correct, qualify, or add evidence to the AI analysis. Every note shown here has been validated by a human moderator.
The AI analysis stands alone for now. Submit a note if you can add evidence or important context.