Skip to content

Commit f37108b

Browse files
author
pananton
committed
feat userver: add void support to utils::expected
Relates: <https://nda.ya.ru/t/wXSs-Og_7Nan57> commit_hash:6f999dc094e94f80e792b7f81aa477cee2bc4d6f
1 parent 4713600 commit f37108b

File tree

3 files changed

+182
-2
lines changed

3 files changed

+182
-2
lines changed

.mapping.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5477,6 +5477,7 @@
54775477
"universal/src/utils/encoding/tskv_test.cpp":"taxi/uservices/userver/universal/src/utils/encoding/tskv_test.cpp",
54785478
"universal/src/utils/encoding/tskv_testdata_bin.hpp":"taxi/uservices/userver/universal/src/utils/encoding/tskv_testdata_bin.hpp",
54795479
"universal/src/utils/enumerate_test.cpp":"taxi/uservices/userver/universal/src/utils/enumerate_test.cpp",
5480+
"universal/src/utils/expected_test.cpp":"taxi/uservices/userver/universal/src/utils/expected_test.cpp",
54805481
"universal/src/utils/fast_pimpl_test.cpp":"taxi/uservices/userver/universal/src/utils/fast_pimpl_test.cpp",
54815482
"universal/src/utils/fast_scope_guard_test.cpp":"taxi/uservices/userver/universal/src/utils/fast_scope_guard_test.cpp",
54825483
"universal/src/utils/filter_bloom_test.cpp":"taxi/uservices/userver/universal/src/utils/filter_bloom_test.cpp",

universal/include/userver/utils/expected.hpp

Lines changed: 94 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ unexpected(E) -> unexpected<E>;
5555
template <class S, class E>
5656
class [[nodiscard]] expected {
5757
public:
58-
constexpr expected() noexcept(std::is_void_v<S>);
58+
constexpr expected();
5959
expected(const S& success);
6060
expected(S&& success);
6161
expected(const unexpected<E>& error);
@@ -70,6 +70,9 @@ class [[nodiscard]] expected {
7070
/// @brief Check whether *this contains an expected value
7171
bool has_value() const noexcept;
7272

73+
/// @brief Check whether *this contains an expected value
74+
explicit operator bool() const noexcept;
75+
7376
/// @brief Return reference to the value or throws bad_expected_access
7477
/// if it's not available
7578
/// @throws utils::bad_expected_access if *this contain an unexpected value
@@ -93,6 +96,30 @@ class [[nodiscard]] expected {
9396
std::variant<S, unexpected<E>> data_;
9497
};
9598

99+
template <class E>
100+
class [[nodiscard]] expected<void, E> {
101+
public:
102+
constexpr expected() noexcept;
103+
expected(const unexpected<E>& error);
104+
expected(unexpected<E>&& error);
105+
106+
template <class G, typename = std::enable_if_t<std::is_convertible_v<G, E>>>
107+
expected(const unexpected<G>& error);
108+
109+
template <class G, typename = std::enable_if_t<std::is_convertible_v<G, E>>>
110+
expected(unexpected<G>&& error);
111+
112+
bool has_value() const noexcept;
113+
explicit operator bool() const noexcept;
114+
void value() const;
115+
116+
E& error();
117+
const E& error() const;
118+
119+
private:
120+
std::variant<std::monostate, unexpected<E>> data_;
121+
};
122+
96123
template <class E>
97124
unexpected<E>::unexpected(const E& error)
98125
: value_{error}
@@ -126,7 +153,7 @@ const E& unexpected<E>::error() const noexcept {
126153
}
127154

128155
template <class S, class E>
129-
constexpr expected<S, E>::expected() noexcept(std::is_void_v<S>)
156+
constexpr expected<S, E>::expected()
130157
: data_(std::in_place_index<0>)
131158
{}
132159

@@ -167,6 +194,11 @@ bool expected<S, E>::has_value() const noexcept {
167194
return std::holds_alternative<S>(data_);
168195
}
169196

197+
template <class S, class E>
198+
expected<S, E>::operator bool() const noexcept {
199+
return has_value();
200+
}
201+
170202
template <class S, class E>
171203
S& expected<S, E>::value() & {
172204
S* result = std::get_if<S>(&data_);
@@ -208,6 +240,66 @@ const E& expected<S, E>::error() const {
208240
return result->error();
209241
}
210242

243+
template <class E>
244+
constexpr expected<void, E>::expected() noexcept: data_(std::in_place_index<0>) {}
245+
246+
template <class E>
247+
expected<void, E>::expected(const unexpected<E>& error)
248+
: data_(error.error())
249+
{}
250+
251+
template <class E>
252+
expected<void, E>::expected(unexpected<E>&& error)
253+
: data_(std::forward<unexpected<E>>(error.error()))
254+
{}
255+
256+
template <class E>
257+
template <class G, typename>
258+
expected<void, E>::expected(const unexpected<G>& error)
259+
: data_(utils::unexpected<E>(std::forward<G>(error.error())))
260+
{}
261+
262+
template <class E>
263+
template <class G, typename>
264+
expected<void, E>::expected(unexpected<G>&& error)
265+
: data_(utils::unexpected<E>(std::forward<G>(error.error())))
266+
{}
267+
268+
template <class E>
269+
bool expected<void, E>::has_value() const noexcept {
270+
return data_.index() == 0;
271+
}
272+
273+
template <class E>
274+
expected<void, E>::operator bool() const noexcept {
275+
return has_value();
276+
}
277+
278+
template <class E>
279+
void expected<void, E>::value() const {
280+
if (!has_value()) {
281+
throw bad_expected_access("Trying to get undefined value from utils::expected");
282+
}
283+
}
284+
285+
template <class E>
286+
E& expected<void, E>::error() {
287+
auto* result = std::get_if<unexpected<E>>(&data_);
288+
if (result == nullptr) {
289+
throw bad_expected_access("Trying to get undefined error value from utils::expected");
290+
}
291+
return result->error();
292+
}
293+
294+
template <class E>
295+
const E& expected<void, E>::error() const {
296+
const auto* result = std::get_if<unexpected<E>>(&data_);
297+
if (result == nullptr) {
298+
throw bad_expected_access("Trying to get undefined error value from utils::expected");
299+
}
300+
return result->error();
301+
}
302+
211303
// NOLINTEND(readability-identifier-naming)
212304

213305
} // namespace utils
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#include <string>
2+
3+
#include <gtest/gtest.h>
4+
5+
#include <userver/utils/expected.hpp>
6+
7+
USERVER_NAMESPACE_BEGIN
8+
9+
using ExpectedInt = utils::expected<int, std::string>;
10+
using ExpectedVoid = utils::expected<void, std::string>;
11+
12+
TEST(Expected, DefaultCtorCreatesValue) {
13+
EXPECT_TRUE(ExpectedInt{}.has_value());
14+
EXPECT_TRUE(ExpectedInt{});
15+
EXPECT_EQ(ExpectedInt{}.value(), 0);
16+
EXPECT_TRUE(ExpectedVoid{}.has_value());
17+
EXPECT_TRUE(ExpectedVoid{});
18+
}
19+
20+
TEST(Expected, ValueCtor) {
21+
ExpectedInt ei{5};
22+
23+
EXPECT_TRUE(ei.has_value());
24+
EXPECT_TRUE(ei);
25+
ASSERT_NO_THROW(ei.value());
26+
EXPECT_EQ(ei.value(), 5);
27+
28+
ei.value() += 10;
29+
30+
EXPECT_TRUE(ei.has_value());
31+
EXPECT_TRUE(ei);
32+
EXPECT_EQ(std::move(ei).value(), 15);
33+
}
34+
35+
TEST(Expected, ErrorCtor) {
36+
auto error = utils::unexpected{std::string("string error")};
37+
38+
ExpectedInt ei{error};
39+
ExpectedVoid ev{std::move(error)};
40+
41+
EXPECT_FALSE(ei.has_value());
42+
EXPECT_FALSE(ei);
43+
EXPECT_FALSE(ev.has_value());
44+
EXPECT_FALSE(ev);
45+
EXPECT_EQ(const_cast<const ExpectedInt&>(ei).error(), "string error");
46+
EXPECT_EQ(const_cast<const ExpectedVoid&>(ev).error(), "string error");
47+
48+
ei.error() = "another error";
49+
ev.error() = "one more error";
50+
51+
EXPECT_EQ(ei.error(), "another error");
52+
EXPECT_EQ(ev.error(), "one more error");
53+
54+
ei = ExpectedInt{utils::unexpected<const char*>("converted error")};
55+
ev = ExpectedVoid{utils::unexpected<const char*>("converted error")};
56+
57+
EXPECT_FALSE(ei.has_value());
58+
EXPECT_FALSE(ei);
59+
EXPECT_FALSE(ev.has_value());
60+
EXPECT_FALSE(ev);
61+
EXPECT_EQ(ei.error(), "converted error");
62+
EXPECT_EQ(ev.error(), "converted error");
63+
}
64+
65+
TEST(Expected, ValueThrowsIfExpectedContainsError) {
66+
auto error = utils::unexpected{std::string("string error")};
67+
68+
ExpectedInt ei{error};
69+
ExpectedVoid ev{std::move(error)};
70+
71+
EXPECT_THROW(const_cast<const ExpectedInt&>(ei).value(), utils::bad_expected_access);
72+
EXPECT_THROW(ei.value(), utils::bad_expected_access);
73+
EXPECT_THROW(std::move(ei).value(), utils::bad_expected_access);
74+
EXPECT_THROW(ev.value(), utils::bad_expected_access);
75+
}
76+
77+
TEST(Expected, ErrorThrowsIfExpectedContainsValue) {
78+
ExpectedInt ei{10};
79+
ExpectedVoid ev;
80+
81+
EXPECT_THROW(const_cast<const ExpectedInt&>(ei).error(), utils::bad_expected_access);
82+
EXPECT_THROW(ei.error(), utils::bad_expected_access);
83+
EXPECT_THROW(const_cast<const ExpectedVoid&>(ev).error(), utils::bad_expected_access);
84+
EXPECT_THROW(ev.error(), utils::bad_expected_access);
85+
}
86+
87+
USERVER_NAMESPACE_END

0 commit comments

Comments
 (0)