-
Notifications
You must be signed in to change notification settings - Fork 38.3k
util: Add some more Unexpected and Expected methods #34032
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: master
Are you sure you want to change the base?
Changes from all commits
fad4a9f
faa109f
fa1de11
fa5a49c
fa696c4
fa71c13
fa9aad7
fa874c9
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -6,9 +6,10 @@ | |
| #define BITCOIN_UTIL_EXPECTED_H | ||
|
|
||
| #include <attributes.h> | ||
| #include <util/check.h> | ||
|
|
||
| #include <cassert> | ||
| #include <type_traits> | ||
| #include <exception> | ||
| #include <utility> | ||
| #include <variant> | ||
|
|
||
|
|
@@ -20,8 +21,18 @@ template <class E> | |
| class Unexpected | ||
| { | ||
| public: | ||
| constexpr explicit Unexpected(E e) : err(std::move(e)) {} | ||
| E err; | ||
| constexpr explicit Unexpected(E e) : m_error(std::move(e)) {} | ||
|
|
||
| constexpr const E& error() const& noexcept LIFETIMEBOUND { return m_error; } | ||
| constexpr E& error() & noexcept LIFETIMEBOUND { return m_error; } | ||
| constexpr E&& error() && noexcept LIFETIMEBOUND { return std::move(m_error); } | ||
|
|
||
| private: | ||
| E m_error; | ||
| }; | ||
|
|
||
| struct BadExpectedAccess : std::exception { | ||
| const char* what() const noexcept override { return "Bad util::Expected access"; } | ||
| }; | ||
|
|
||
| /// The util::Expected class provides a standard way for low-level functions to | ||
|
|
@@ -33,58 +44,87 @@ 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.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 const ValueType& value() const LIFETIMEBOUND | ||
| constexpr const T& value() const& LIFETIMEBOUND | ||
| { | ||
| assert(has_value()); | ||
| if (!has_value()) { | ||
| throw BadExpectedAccess{}; | ||
| } | ||
| return std::get<0>(m_data); | ||
| } | ||
| constexpr ValueType& value() LIFETIMEBOUND | ||
| constexpr T& value() & LIFETIMEBOUND | ||
| { | ||
| assert(has_value()); | ||
| if (!has_value()) { | ||
| throw BadExpectedAccess{}; | ||
| } | ||
| return std::get<0>(m_data); | ||
| } | ||
| constexpr T&& value() && LIFETIMEBOUND { return std::move(value()); } | ||
|
|
||
| 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); | ||
ryanofsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| } | ||
|
|
||
| constexpr const E& error() const LIFETIMEBOUND | ||
| 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()); } | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How come this method overload is not calling itself recursively? Tested adding Is
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
It is not
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can't read that. 😁
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
i'd say it says that |
||
|
|
||
| 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(); } | ||
|
Comment on lines
+93
to
+98
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nit: Might be nice to have these assert before failing due to an exception being thrown inside of
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Why? Just seems more code for no reason?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It's about failing with a clearer stack trace/message. An exception will unwind the stack before giving a stack-trace (depending upon how the compiler treats exceptions within Tried adding
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yes, this is also the behavior that you get in C++23 (if you are lucky). If you are unlucky, you'll just get the silent UB: https://godbolt.org/z/jYxqvoejn Also, this is the behavior with an assert/abort, so I don't see the difference!?
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Yeah, we don't need to spoil ourselves before the switcharoo to |
||
| }; | ||
|
|
||
| template <class E> | ||
| class Expected<void, E> | ||
|
Comment on lines
+101
to
+102
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. In commit "util: Add Expected<void, E> specialization" (fa3d6c7) This specialization is more complicated than it needs to be. Would suggest simplifying with inheritance: //! Specialization for void returning void from value methods.
template <class E>
class Expected<void, E> : public Expected<std::monostate, E>
{
using Base = Expected<std::monostate, E>;
public:
using Expected<std::monostate, E>::Expected;
constexpr void operator*() const noexcept { Base::operator*(); }
constexpr void value() const& { Base::value(); }
constexpr void value() && { Base::value(); }
void value_or() = delete;
};
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This just brings back the problem we are trying to solve: Solve exposing monostate externally?
|
||
| { | ||
| private: | ||
| std::variant<std::monostate, E> m_data; | ||
ryanofsky marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| 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()} | ||
| { | ||
| assert(!has_value()); | ||
| return std::get<1>(m_data); | ||
| } | ||
| constexpr E& error() LIFETIMEBOUND | ||
|
|
||
| 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 {} | ||
| constexpr void value() const | ||
| { | ||
| assert(!has_value()); | ||
| return std::get<1>(m_data); | ||
| if (!has_value()) { | ||
| throw BadExpectedAccess{}; | ||
| } | ||
| } | ||
|
|
||
| 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(); } | ||
| 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()); } | ||
| }; | ||
maflcko marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
|
||
| } // namespace util | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.