From fad4a9fe2b8d3a3aa09eca4f47e1741912328785 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 08:24:42 +0100 Subject: [PATCH 1/8] Set bugprone-unused-return-value.AllowCastToVoid It only makes sense to turn this off with C++26, which introduces the _ placeholder. --- src/.clang-tidy | 2 ++ src/bench/coin_selection.cpp | 2 +- src/httpserver.cpp | 2 +- src/ipc/test/ipc_test.cpp | 4 +--- src/wallet/test/fuzz/spend.cpp | 2 +- 5 files changed, 6 insertions(+), 6 deletions(-) diff --git a/src/.clang-tidy b/src/.clang-tidy index da53a588c2e..0f69a0b0b3a 100644 --- a/src/.clang-tidy +++ b/src/.clang-tidy @@ -39,3 +39,5 @@ CheckOptions: value: false - key: bugprone-unused-return-value.CheckedReturnTypes value: '^::std::error_code$;^::std::error_condition$;^::std::errc$;^::std::expected$;^::util::Result$;^::util::Expected$' + - key: bugprone-unused-return-value.AllowCastToVoid + value: true # Can be removed with C++26 once the _ placeholder exists. diff --git a/src/bench/coin_selection.cpp b/src/bench/coin_selection.cpp index 2150d800c9f..04de59908e5 100644 --- a/src/bench/coin_selection.cpp +++ b/src/bench/coin_selection.cpp @@ -130,7 +130,7 @@ static void BnBExhaustion(benchmark::Bench& bench) bench.run([&] { // Benchmark CAmount target = make_hard_case(17, utxo_pool); - [[maybe_unused]] auto _{SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT)}; // Should exhaust + (void)SelectCoinsBnB(utxo_pool, target, /*cost_of_change=*/0, MAX_STANDARD_TX_WEIGHT); // Should exhaust // Cleanup utxo_pool.clear(); diff --git a/src/httpserver.cpp b/src/httpserver.cpp index abfcb455e14..aa648151abb 100644 --- a/src/httpserver.cpp +++ b/src/httpserver.cpp @@ -330,7 +330,7 @@ static void http_request_cb(struct evhttp_request* req, void* arg) std::unique_ptr item(new HTTPWorkItem(std::move(hreq), path, i->handler)); assert(g_work_queue); if (g_work_queue->Enqueue(item.get())) { - [[maybe_unused]] auto _{item.release()}; /* if true, queue took ownership */ + (void)item.release(); /* if true, queue took ownership */ } else { LogWarning("Request rejected because http work queue depth exceeded, it can be increased with the -rpcworkqueue= setting"); item->req->WriteReply(HTTP_SERVICE_UNAVAILABLE, "Work queue depth exceeded"); diff --git a/src/ipc/test/ipc_test.cpp b/src/ipc/test/ipc_test.cpp index 506facdecf3..1acfb01ca85 100644 --- a/src/ipc/test/ipc_test.cpp +++ b/src/ipc/test/ipc_test.cpp @@ -63,9 +63,7 @@ void IpcPipeTest() auto foo_client = std::make_unique>( connection_client->m_rpc_system->bootstrap(mp::ServerVatId().vat_id).castAs(), connection_client.get(), /* destroy_connection= */ true); - { - [[maybe_unused]] auto _{connection_client.release()}; - } + (void)connection_client.release(); foo_promise.set_value(std::move(foo_client)); auto connection_server = std::make_unique(loop, kj::mv(pipe.ends[1]), [&](mp::Connection& connection) { diff --git a/src/wallet/test/fuzz/spend.cpp b/src/wallet/test/fuzz/spend.cpp index 99bc5345a34..552364a667d 100644 --- a/src/wallet/test/fuzz/spend.cpp +++ b/src/wallet/test/fuzz/spend.cpp @@ -98,7 +98,7 @@ FUZZ_TARGET(wallet_create_transaction, .init = initialize_setup) std::optional change_pos; if (fuzzed_data_provider.ConsumeBool()) change_pos = fuzzed_data_provider.ConsumeIntegral(); - [[maybe_unused]] auto _{CreateTransaction(*fuzzed_wallet.wallet, recipients, change_pos, coin_control)}; + (void)CreateTransaction(*fuzzed_wallet.wallet, recipients, change_pos, coin_control); } } // namespace } // namespace wallet From faa109f8be7fca125c55ca84e6c0baf414c59ae6 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Wed, 10 Dec 2025 17:39:30 +0100 Subject: [PATCH 2/8] test: refactor: Use BOOST_CHECK_EQUAL over BOOST_CHECK == --- src/test/util_expected_tests.cpp | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index 61a6d2e0745..67a1a5fb52c 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -17,15 +17,15 @@ BOOST_AUTO_TEST_CASE(expected_value) int x; }; Expected e{}; - BOOST_CHECK(e.value().x == 0); + BOOST_CHECK_EQUAL(e.value().x, 0); e = Obj{42}; BOOST_CHECK(e.has_value()); BOOST_CHECK(static_cast(e)); - BOOST_CHECK(e.value().x == 42); - BOOST_CHECK((*e).x == 42); - BOOST_CHECK(e->x == 42); + BOOST_CHECK_EQUAL(e.value().x, 42); + BOOST_CHECK_EQUAL((*e).x, 42); + BOOST_CHECK_EQUAL(e->x, 42); // modify value e.value().x += 1; @@ -33,9 +33,9 @@ BOOST_AUTO_TEST_CASE(expected_value) e->x += 1; const auto& read{e}; - BOOST_CHECK(read.value().x == 45); - BOOST_CHECK((*read).x == 45); - BOOST_CHECK(read->x == 45); + BOOST_CHECK_EQUAL(read.value().x, 45); + BOOST_CHECK_EQUAL((*read).x, 45); + BOOST_CHECK_EQUAL(read->x, 45); } BOOST_AUTO_TEST_CASE(expected_value_or) @@ -56,13 +56,13 @@ BOOST_AUTO_TEST_CASE(expected_error) e = Unexpected{"fail"}; BOOST_CHECK(!e.has_value()); BOOST_CHECK(!static_cast(e)); - BOOST_CHECK(e.error() == "fail"); + BOOST_CHECK_EQUAL(e.error(), "fail"); // modify error e.error() += "1"; const auto& read{e}; - BOOST_CHECK(read.error() == "fail1"); + BOOST_CHECK_EQUAL(read.error(), "fail1"); } BOOST_AUTO_TEST_SUITE_END() From fa1de1103fe5d97ddddc9e45286e32751151f859 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 08:40:31 +0100 Subject: [PATCH 3/8] util: Add Unexpected::error() This is not needed, but a bit closer to the std lib. --- src/test/util_expected_tests.cpp | 18 ++++++++++++++++++ src/util/expected.h | 12 +++++++++--- 2 files changed, 27 insertions(+), 3 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index 67a1a5fb52c..f02ea3fdd70 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -7,6 +7,11 @@ #include +#include +#include +#include + + using namespace util; BOOST_AUTO_TEST_SUITE(util_expected_tests) @@ -65,4 +70,17 @@ BOOST_AUTO_TEST_CASE(expected_error) BOOST_CHECK_EQUAL(read.error(), "fail1"); } +BOOST_AUTO_TEST_CASE(unexpected_error_accessors) +{ + Unexpected u{std::make_unique(-1)}; + BOOST_CHECK_EQUAL(*u.error(), -1); + + *u.error() -= 1; + const auto& read{u}; + BOOST_CHECK_EQUAL(*read.error(), -2); + + const auto moved{std::move(u).error()}; + BOOST_CHECK_EQUAL(*moved, -2); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/expected.h b/src/util/expected.h index 0e7256f8677..71cd0f941c8 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -20,8 +20,14 @@ template 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; }; /// The util::Expected class provides a standard way for low-level functions to @@ -40,7 +46,7 @@ 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 - constexpr Expected(Unexpected u) : m_data{std::in_place_index_t<1>{}, std::move(u.err)} + constexpr Expected(Unexpected u) : m_data{std::in_place_index_t<1>{}, std::move(u).error()} { } From fa6575d6c2d27d173162888226df669fb8aeea47 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 07:50:41 +0100 Subject: [PATCH 4/8] util: Make Expected::value() throw This is not expected to be needed in this codebase, but brings the implementation closer to std::expected::value(). Also, add noexcept, where std::expected has them. This will make operator-> and operator* terminate, when has_value() is false. --- src/test/util_expected_tests.cpp | 9 +++++++++ src/util/expected.h | 21 +++++++++++++++------ 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index f02ea3fdd70..64f543bf3fd 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -53,6 +53,15 @@ BOOST_AUTO_TEST_CASE(expected_value_or) BOOST_CHECK_EQUAL(const_val.value_or("fallback"), "fallback"); } +BOOST_AUTO_TEST_CASE(expected_value_throws) +{ + const Expected e{Unexpected{"fail"}}; + BOOST_CHECK_THROW(e.value(), BadExpectedAccess); + + const Expected void_e{Unexpected{"fail"}}; + BOOST_CHECK_THROW(void_e.value(), BadExpectedAccess); +} + BOOST_AUTO_TEST_CASE(expected_error) { Expected e{}; diff --git a/src/util/expected.h b/src/util/expected.h index 71cd0f941c8..d4611698607 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -8,6 +8,7 @@ #include #include +#include #include #include #include @@ -30,6 +31,10 @@ 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 /// return either error values or result values. /// @@ -55,12 +60,16 @@ public: constexpr const ValueType& value() const LIFETIMEBOUND { - assert(has_value()); + if (!has_value()) { + throw BadExpectedAccess{}; + } return std::get<0>(m_data); } constexpr ValueType& value() LIFETIMEBOUND { - assert(has_value()); + if (!has_value()) { + throw BadExpectedAccess{}; + } return std::get<0>(m_data); } @@ -86,11 +95,11 @@ public: return std::get<1>(m_data); } - constexpr ValueType& operator*() LIFETIMEBOUND { return value(); } - constexpr const ValueType& operator*() const LIFETIMEBOUND { return value(); } + constexpr ValueType& operator*() noexcept LIFETIMEBOUND { return value(); } + constexpr const ValueType& operator*() const noexcept LIFETIMEBOUND { return value(); } - constexpr ValueType* operator->() LIFETIMEBOUND { return &value(); } - constexpr const ValueType* operator->() const LIFETIMEBOUND { return &value(); } + constexpr ValueType* operator->() noexcept LIFETIMEBOUND { return &value(); } + constexpr const ValueType* operator->() const noexcept LIFETIMEBOUND { return &value(); } }; } // namespace util From fac48009598611d28b6583559af513c337166aeb Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 09:10:21 +0100 Subject: [PATCH 5/8] util: Add Expected specialization This is not needed, but a bit closer to the std lib, because std::monostate is no longer leaked through ValueType from the value() method. --- src/test/util_expected_tests.cpp | 2 ++ src/util/expected.h | 56 ++++++++++++++++++++++++-------- 2 files changed, 44 insertions(+), 14 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index 64f543bf3fd..e626a9887c7 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 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 d4611698607..a7d02c39dcc 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -6,10 +6,10 @@ #define BITCOIN_UTIL_EXPECTED_H #include +#include #include #include -#include #include #include @@ -44,28 +44,27 @@ template class Expected { private: - using ValueType = std::conditional_t, std::monostate, T>; - std::variant m_data; + std::variant 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 - constexpr Expected(Unexpected u) : m_data{std::in_place_index_t<1>{}, std::move(u).error()} + constexpr Expected(Unexpected 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 - ValueType value_or(U&& default_value) const& + T value_or(U&& default_value) const& { return has_value() ? value() : std::forward(default_value); } template - ValueType value_or(U&& default_value) && + T value_or(U&& default_value) && { return has_value() ? std::move(value()) : std::forward(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 Expected +{ +private: + std::variant m_data; + +public: + constexpr Expected() : m_data{std::in_place_index<0>, std::monostate{}} {} + template + constexpr Expected(Unexpected 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 From fab9721430aa83ddb266aca029e270aec81c021d Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 09:26:17 +0100 Subject: [PATCH 6/8] util: Implement Expected::value()&& and Expected::error()&& They are currently unused, but implementing them is closer to the std::expected. --- src/test/util_expected_tests.cpp | 21 +++++++++++++++++++++ src/util/expected.h | 18 ++++++------------ 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index e626a9887c7..7c9d62ad392 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -43,6 +43,13 @@ BOOST_AUTO_TEST_CASE(expected_value) BOOST_CHECK_EQUAL(read->x, 45); } +BOOST_AUTO_TEST_CASE(expected_value_rvalue) +{ + Expected, int> no_copy{std::make_unique(5)}; + const auto moved{std::move(no_copy).value()}; + BOOST_CHECK_EQUAL(*moved, 5); +} + BOOST_AUTO_TEST_CASE(expected_value_or) { Expected, int> no_copy{std::make_unique(1)}; @@ -81,6 +88,20 @@ BOOST_AUTO_TEST_CASE(expected_error) BOOST_CHECK_EQUAL(read.error(), "fail1"); } +BOOST_AUTO_TEST_CASE(expected_error_rvalue) +{ + { + Expected> nocopy_err{Unexpected{std::make_unique(7)}}; + const auto moved{std::move(nocopy_err).error()}; + BOOST_CHECK_EQUAL(*moved, 7); + } + { + Expected> void_nocopy_err{Unexpected{std::make_unique(9)}}; + const auto moved{std::move(void_nocopy_err).error()}; + BOOST_CHECK_EQUAL(*moved, 9); + } +} + BOOST_AUTO_TEST_CASE(unexpected_error_accessors) { Unexpected u{std::make_unique(-1)}; diff --git a/src/util/expected.h b/src/util/expected.h index a7d02c39dcc..85d4bf9d47a 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -57,20 +57,21 @@ public: constexpr bool has_value() const noexcept { return m_data.index() == 0; } constexpr explicit operator bool() const noexcept { return has_value(); } - constexpr const T& value() const LIFETIMEBOUND + constexpr const T& value() const& LIFETIMEBOUND { if (!has_value()) { throw BadExpectedAccess{}; } return std::get<0>(m_data); } - constexpr T& value() LIFETIMEBOUND + constexpr T& value() & LIFETIMEBOUND { if (!has_value()) { throw BadExpectedAccess{}; } return std::get<0>(m_data); } + constexpr T&& value() && LIFETIMEBOUND { return std::move(value()); } template T value_or(U&& default_value) const& @@ -83,16 +84,9 @@ public: return has_value() ? std::move(value()) : std::forward(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 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 T& operator*() noexcept LIFETIMEBOUND { return value(); } constexpr const T& operator*() const noexcept LIFETIMEBOUND { return value(); } From fabb47e4e3dba7c03f9242440cb55eb37b493a7a Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Tue, 9 Dec 2025 16:17:56 +0100 Subject: [PATCH 7/8] util: Implement Expected::operator*()&& It is currently unused, but implementing it is closer to std::expected. --- src/test/util_expected_tests.cpp | 7 +++++++ src/util/expected.h | 5 +++-- 2 files changed, 10 insertions(+), 2 deletions(-) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index 7c9d62ad392..356735b221e 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -50,6 +50,13 @@ BOOST_AUTO_TEST_CASE(expected_value_rvalue) BOOST_CHECK_EQUAL(*moved, 5); } +BOOST_AUTO_TEST_CASE(expected_deref_rvalue) +{ + Expected, int> no_copy{std::make_unique(5)}; + const auto moved{*std::move(no_copy)}; + BOOST_CHECK_EQUAL(*moved, 5); +} + BOOST_AUTO_TEST_CASE(expected_value_or) { Expected, int> no_copy{std::make_unique(1)}; diff --git a/src/util/expected.h b/src/util/expected.h index 85d4bf9d47a..b01d866a863 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -88,8 +88,9 @@ public: constexpr E& error() & noexcept LIFETIMEBOUND { return *Assert(std::get_if<1>(&m_data)); } constexpr E&& error() && noexcept LIFETIMEBOUND { return std::move(error()); } - constexpr T& operator*() noexcept LIFETIMEBOUND { return value(); } - constexpr const T& operator*() const noexcept LIFETIMEBOUND { return value(); } + 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(); } From faa59b367985648df901bdd7b5bba69ef898ea08 Mon Sep 17 00:00:00 2001 From: MarcoFalke <*~=`'#}+{/-|&$^_@721217.xyz> Date: Thu, 11 Dec 2025 11:02:33 +0100 Subject: [PATCH 8/8] util: Add Expected::swap() --- src/test/util_expected_tests.cpp | 9 +++++++++ src/util/expected.h | 2 ++ 2 files changed, 11 insertions(+) diff --git a/src/test/util_expected_tests.cpp b/src/test/util_expected_tests.cpp index 356735b221e..5979250ea90 100644 --- a/src/test/util_expected_tests.cpp +++ b/src/test/util_expected_tests.cpp @@ -122,4 +122,13 @@ BOOST_AUTO_TEST_CASE(unexpected_error_accessors) BOOST_CHECK_EQUAL(*moved, -2); } +BOOST_AUTO_TEST_CASE(expected_swap) +{ + Expected> a{Unexpected{std::make_unique(-1)}}; + Expected> b{"good"}; + a.swap(b); + BOOST_CHECK_EQUAL(a.value(), "good"); + BOOST_CHECK_EQUAL(*b.error(), -1); +} + BOOST_AUTO_TEST_SUITE_END() diff --git a/src/util/expected.h b/src/util/expected.h index b01d866a863..66fb98e0558 100644 --- a/src/util/expected.h +++ b/src/util/expected.h @@ -88,6 +88,8 @@ public: 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()); }