Commit e6f6dab9 authored by Dominik Charousset's avatar Dominik Charousset

Merge branch 'topic/neverlord/expected'

parents d9aca2e1 747abb49
...@@ -24,6 +24,8 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -24,6 +24,8 @@ is based on [Keep a Changelog](https://keepachangelog.com).
- Not initializing the meta objects table now prints a diagnosis message before - Not initializing the meta objects table now prints a diagnosis message before
aborting the program. Previously, the application would usually just crash due aborting the program. Previously, the application would usually just crash due
to a `nullptr`-access inside some CAF function. to a `nullptr`-access inside some CAF function.
- The class `expected` now implements the monadic member functions from C++23
`std::expected` as well as `value_or`.
### Fixed ### Fixed
...@@ -46,6 +48,14 @@ is based on [Keep a Changelog](https://keepachangelog.com). ...@@ -46,6 +48,14 @@ is based on [Keep a Changelog](https://keepachangelog.com).
actors or activities, scheduled actors now limit the amount of actions that actors or activities, scheduled actors now limit the amount of actions that
may run in one iteration (#1364). may run in one iteration (#1364).
### Deprecated
- All member functions from `caf::expected` that have no equivalent in
`std::expected` are now deprecated. Further, `caf::expected<unit_t>` as well
as constructing from `unit_t` are deprecated as well. The reasoning behind
this decision is that `caf::expected` should eventually become an alias for
`std::expected<T, caf::error>`.
## [0.19.0-rc.1] - 2022-10-31 ## [0.19.0-rc.1] - 2022-10-31
### Added ### Added
......
...@@ -46,9 +46,10 @@ void caf_main(actor_system& system) { ...@@ -46,9 +46,10 @@ void caf_main(actor_system& system) {
auto cell2 = system.spawn(unchecked_cell); auto cell2 = system.spawn(unchecked_cell);
// --(rst-spawn-cell-end)-- // --(rst-spawn-cell-end)--
auto f = make_function_view(cell1); auto f = make_function_view(cell1);
cout << "cell value: " << f(get_atom_v) << endl; cout << "cell value: " << caf::to_string(f(get_atom_v)) << endl;
f(put_atom_v, 20); f(put_atom_v, 20);
cout << "cell value (after setting to 20): " << f(get_atom_v) << endl; cout << "cell value (after setting to 20): " << caf::to_string(f(get_atom_v))
<< endl;
// Get an unchecked cell and send it some garbage. Triggers an "unexpected // Get an unchecked cell and send it some garbage. Triggers an "unexpected
// message" error (and terminates cell2!). // message" error (and terminates cell2!).
anon_send(cell2, "hello there!"); anon_send(cell2, "hello there!");
......
...@@ -125,6 +125,7 @@ std::ostream& operator<<(std::ostream& out, const expected<int>& x) { ...@@ -125,6 +125,7 @@ std::ostream& operator<<(std::ostream& out, const expected<int>& x) {
} }
void caf_main(actor_system& sys) { void caf_main(actor_system& sys) {
using std::cout;
// Spawn our matrix. // Spawn our matrix.
static constexpr int rows = 3; static constexpr int rows = 3;
static constexpr int columns = 6; static constexpr int columns = 6;
...@@ -140,18 +141,18 @@ void caf_main(actor_system& sys) { ...@@ -140,18 +141,18 @@ void caf_main(actor_system& sys) {
// Print out matrix. // Print out matrix.
for (int row = 0; row < rows; ++row) { for (int row = 0; row < rows; ++row) {
for (int column = 0; column < columns; ++column) for (int column = 0; column < columns; ++column)
std::cout << std::setw(4) << f(get_atom_v, row, column) << ' '; cout << std::setw(4) << f(get_atom_v, row, column) << ' ';
std::cout << '\n'; cout << '\n';
} }
// Print out AVG for each row and column. // Print out AVG for each row and column.
for (int row = 0; row < rows; ++row) for (int row = 0; row < rows; ++row)
std::cout << "AVG(row " << row cout << "AVG(row " << row << ") = "
<< ") = " << f(get_atom_v, average_atom_v, row_atom_v, row) << caf::to_string(f(get_atom_v, average_atom_v, row_atom_v, row))
<< '\n'; << '\n';
for (int column = 0; column < columns; ++column) for (int column = 0; column < columns; ++column)
std::cout << "AVG(column " << column cout << "AVG(column " << column << ") = "
<< ") = " << f(get_atom_v, average_atom_v, column_atom_v, column) << caf::to_string(f(get_atom_v, average_atom_v, column_atom_v, column))
<< '\n'; << '\n';
} }
CAF_MAIN(id_block::fan_out_request) CAF_MAIN(id_block::fan_out_request)
...@@ -94,7 +94,8 @@ void client_repl(function_view<calculator> f) { ...@@ -94,7 +94,8 @@ void client_repl(function_view<calculator> f) {
usage(); usage();
else else
cout << " = " cout << " = "
<< (words[1] == "+" ? f(add_atom_v, *x, *y) : f(sub_atom_v, *x, *y)) << caf::to_string(words[1] == "+" ? f(add_atom_v, *x, *y)
: f(sub_atom_v, *x, *y))
<< "\n"; << "\n";
} }
} }
......
...@@ -544,6 +544,9 @@ struct is_expected : std::false_type {}; ...@@ -544,6 +544,9 @@ struct is_expected : std::false_type {};
template <class T> template <class T>
struct is_expected<expected<T>> : std::true_type {}; struct is_expected<expected<T>> : std::true_type {};
template <class T>
constexpr bool is_expected_v = is_expected<T>::value;
// Checks whether `T` and `U` are integers of the same size and signedness. // Checks whether `T` and `U` are integers of the same size and signedness.
// clang-format off // clang-format off
template <class T, class U, template <class T, class U,
......
...@@ -10,24 +10,47 @@ ...@@ -10,24 +10,47 @@
#include <new> #include <new>
#include <ostream> #include <ostream>
#include <type_traits> #include <type_traits>
#include <utility>
#include "caf/deep_to_string.hpp" #include "caf/deep_to_string.hpp"
#include "caf/detail/type_traits.hpp"
#include "caf/error.hpp" #include "caf/error.hpp"
#include "caf/is_error_code_enum.hpp" #include "caf/is_error_code_enum.hpp"
#include "caf/raise_error.hpp"
#include "caf/unit.hpp" #include "caf/unit.hpp"
namespace caf { namespace caf {
namespace detail {
template <class F, class... Ts>
auto expected_from_fn(F&& f, Ts&&... xs) {
using res_t = decltype(f(std::forward<Ts>(xs)...));
if constexpr (std::is_void_v<res_t>) {
f(std::forward<Ts>(xs)...);
return expected<res_t>{};
} else {
return expected<res_t>{f(std::forward<Ts>(xs)...)};
}
}
} // namespace detail
/// Represents the result of a computation which can either complete /// Represents the result of a computation which can either complete
/// successfully with an instance of type `T` or fail with an `error`. /// successfully with an instance of type `T` or fail with an `error`.
/// @tparam T The type of the result. /// @tparam T The type of the result.
template <typename T> template <class T>
class expected { class expected {
public: public:
// -- member types ----------------------------------------------------------- // -- member types -----------------------------------------------------------
using value_type = T; using value_type = T;
using error_type = caf::error;
template <class U>
using rebind = expected<U>;
// -- static member variables ------------------------------------------------ // -- static member variables ------------------------------------------------
/// Stores whether move construct and move assign never throw. /// Stores whether move construct and move assign never throw.
...@@ -40,25 +63,32 @@ public: ...@@ -40,25 +63,32 @@ public:
= std::is_nothrow_copy_constructible<T>::value = std::is_nothrow_copy_constructible<T>::value
&& std::is_nothrow_copy_assignable<T>::value; && std::is_nothrow_copy_assignable<T>::value;
/// Stores whether swap() never throws.
static constexpr bool nothrow_swap = std::is_nothrow_swappable_v<T>;
// -- constructors, destructors, and assignment operators -------------------- // -- constructors, destructors, and assignment operators --------------------
template <class U> template <class U, class = std::enable_if_t<std::is_convertible_v<U, T>
expected( || is_error_code_enum_v<U>>>
U x, expected(U x) {
typename std::enable_if<std::is_convertible<U, T>::value>::type* = nullptr) if constexpr (std::is_convertible_v<U, T>) {
: engaged_(true) { has_value_ = true;
new (std::addressof(value_)) T(std::move(x)); new (std::addressof(value_)) T(std::move(x));
} else {
has_value_ = false;
new (std::addressof(error_)) caf::error(x);
}
} }
expected(T&& x) noexcept(nothrow_move) : engaged_(true) { expected(T&& x) noexcept(nothrow_move) : has_value_(true) {
new (std::addressof(value_)) T(std::move(x)); new (std::addressof(value_)) T(std::move(x));
} }
expected(const T& x) noexcept(nothrow_copy) : engaged_(true) { expected(const T& x) noexcept(nothrow_copy) : has_value_(true) {
new (std::addressof(value_)) T(x); new (std::addressof(value_)) T(x);
} }
expected(caf::error e) noexcept : engaged_(false) { expected(caf::error e) noexcept : has_value_(false) {
new (std::addressof(error_)) caf::error{std::move(e)}; new (std::addressof(error_)) caf::error{std::move(e)};
} }
...@@ -66,23 +96,23 @@ public: ...@@ -66,23 +96,23 @@ public:
construct(other); construct(other);
} }
template <class Enum, class = std::enable_if_t<is_error_code_enum_v<Enum>>>
expected(Enum code) : engaged_(false) {
new (std::addressof(error_)) caf::error(code);
}
expected(expected&& other) noexcept(nothrow_move) { expected(expected&& other) noexcept(nothrow_move) {
construct(std::move(other)); construct(std::move(other));
} }
template <class... Ts>
expected(std::in_place_t, Ts&&... xs) : has_value_(true) {
new (std::addressof(value_)) T(std::forward<Ts>(xs)...);
}
~expected() { ~expected() {
destroy(); destroy();
} }
expected& operator=(const expected& other) noexcept(nothrow_copy) { expected& operator=(const expected& other) noexcept(nothrow_copy) {
if (engaged_ && other.engaged_) if (has_value() && other.has_value_)
value_ = other.value_; value_ = other.value_;
else if (!engaged_ && !other.engaged_) else if (!has_value() && !other.has_value_)
error_ = other.error_; error_ = other.error_;
else { else {
destroy(); destroy();
...@@ -92,9 +122,9 @@ public: ...@@ -92,9 +122,9 @@ public:
} }
expected& operator=(expected&& other) noexcept(nothrow_move) { expected& operator=(expected&& other) noexcept(nothrow_move) {
if (engaged_ && other.engaged_) if (has_value() && other.has_value_)
value_ = std::move(other.value_); value_ = std::move(other.value_);
else if (!engaged_ && !other.engaged_) else if (!has_value() && !other.has_value_)
error_ = std::move(other.error_); error_ = std::move(other.error_);
else { else {
destroy(); destroy();
...@@ -104,22 +134,22 @@ public: ...@@ -104,22 +134,22 @@ public:
} }
expected& operator=(const T& x) noexcept(nothrow_copy) { expected& operator=(const T& x) noexcept(nothrow_copy) {
if (engaged_) { if (has_value()) {
value_ = x; value_ = x;
} else { } else {
destroy(); destroy();
engaged_ = true; has_value_ = true;
new (std::addressof(value_)) T(x); new (std::addressof(value_)) T(x);
} }
return *this; return *this;
} }
expected& operator=(T&& x) noexcept(nothrow_move) { expected& operator=(T&& x) noexcept(nothrow_move) {
if (engaged_) { if (has_value()) {
value_ = std::move(x); value_ = std::move(x);
} else { } else {
destroy(); destroy();
engaged_ = true; has_value_ = true;
new (std::addressof(value_)) T(std::move(x)); new (std::addressof(value_)) T(std::move(x));
} }
return *this; return *this;
...@@ -132,11 +162,11 @@ public: ...@@ -132,11 +162,11 @@ public:
} }
expected& operator=(caf::error e) noexcept { expected& operator=(caf::error e) noexcept {
if (!engaged_) if (!has_value())
error_ = std::move(e); error_ = std::move(e);
else { else {
destroy(); destroy();
engaged_ = false; has_value_ = false;
new (std::addressof(error_)) caf::error(std::move(e)); new (std::addressof(error_)) caf::error(std::move(e));
} }
return *this; return *this;
...@@ -147,106 +177,387 @@ public: ...@@ -147,106 +177,387 @@ public:
return *this = make_error(code); return *this = make_error(code);
} }
// -- observers --------------------------------------------------------------
/// @copydoc engaged
explicit operator bool() const noexcept {
return has_value_;
}
/// Returns `true` if the object holds a value (is engaged).
bool has_value() const noexcept {
return has_value_;
}
/// Returns `true` if the object holds a value (is engaged).
[[deprecated("use has_value() instead")]] bool engaged() const noexcept {
return has_value_;
}
// -- modifiers -------------------------------------------------------------- // -- modifiers --------------------------------------------------------------
template <class... Args>
std::enable_if_t<std::is_nothrow_constructible_v<T, Args...>, T&>
emplace(Args&&... args) noexcept {
destroy();
has_value_ = true;
new (std::addressof(value_)) T(std::forward<Args>(args)...);
return value_;
}
void swap(expected& other) noexcept(nothrow_move&& nothrow_swap) {
expected tmp{std::move(other)};
other = std::move(*this);
*this = std::move(tmp);
}
// -- value access -----------------------------------------------------------
/// Returns the contained value.
/// @pre `has_value() == true`.
[[deprecated("use value() instead")]] const T& cvalue() const noexcept {
if (!has_value())
CAF_RAISE_ERROR("bad_expected_access");
return value_;
}
/// @copydoc cvalue /// @copydoc cvalue
T& value() noexcept { T& value() & {
CAF_ASSERT(engaged_); if (!has_value())
CAF_RAISE_ERROR("bad_expected_access");
return value_; return value_;
} }
/// @copydoc cvalue /// @copydoc cvalue
T& operator*() noexcept { const T& value() const& {
return value(); if (!has_value())
CAF_RAISE_ERROR("bad_expected_access");
return value_;
} }
/// @copydoc cvalue /// @copydoc cvalue
T* operator->() noexcept { T&& value() && {
return &value(); if (!has_value())
CAF_RAISE_ERROR("bad_expected_access");
return std::move(value_);
} }
/// @copydoc cerror /// @copydoc cvalue
caf::error& error() noexcept { const T&& value() const&& {
CAF_ASSERT(!engaged_); if (!has_value())
return error_; CAF_RAISE_ERROR("bad_expected_access");
return std::move(value_);
} }
// -- observers -------------------------------------------------------------- /// Returns the contained value if there is one, otherwise returns `fallback`.
template <class U>
T value_or(U&& fallback) const& {
if (has_value())
return value_;
else
return T{std::forward<U>(fallback)};
}
/// Returns the contained value. /// Returns the contained value if there is one, otherwise returns `fallback`.
/// @pre `engaged() == true`. template <class U>
const T& cvalue() const noexcept { T value_or(U&& fallback) && {
CAF_ASSERT(engaged_); if (has_value())
return std::move(value_);
else
return T{std::forward<U>(fallback)};
}
/// @copydoc cvalue
T& operator*() & noexcept {
return value_; return value_;
} }
/// @copydoc cvalue /// @copydoc cvalue
const T& value() const noexcept { const T& operator*() const& noexcept {
CAF_ASSERT(engaged_);
return value_; return value_;
} }
/// @copydoc cvalue /// @copydoc cvalue
const T& operator*() const noexcept { T&& operator*() && noexcept {
return value(); return std::move(value_);
} }
/// @copydoc cvalue /// @copydoc cvalue
const T* operator->() const noexcept { const T&& operator*() const&& noexcept {
return &value(); return std::move(value_);
} }
/// @copydoc engaged /// @copydoc cvalue
explicit operator bool() const noexcept { T* operator->() noexcept {
return engaged(); return &value_;
} }
/// Returns `true` if the object holds a value (is engaged). /// @copydoc cvalue
bool engaged() const noexcept { const T* operator->() const noexcept {
return engaged_; return &value_;
} }
// -- error access -----------------------------------------------------------
/// Returns the contained error. /// Returns the contained error.
/// @pre `engaged() == false`. /// @pre `has_value() == false`.
const caf::error& cerror() const noexcept { [[deprecated("use error() instead")]] const caf::error&
CAF_ASSERT(!engaged_); cerror() const noexcept {
CAF_ASSERT(!has_value_);
return error_; return error_;
} }
/// @copydoc cerror /// @copydoc cerror
const caf::error& error() const noexcept { caf::error& error() & noexcept {
CAF_ASSERT(!engaged_); CAF_ASSERT(!has_value_);
return error_; return error_;
} }
/// @copydoc cerror
const caf::error& error() const& noexcept {
CAF_ASSERT(!has_value_);
return error_;
}
/// @copydoc cerror
caf::error&& error() && noexcept {
CAF_ASSERT(!has_value_);
return std::move(error_);
}
/// @copydoc cerror
const caf::error&& error() const&& noexcept {
CAF_ASSERT(!has_value_);
return std::move(error_);
}
// -- monadic functions ------------------------------------------------------
template <class F>
auto and_then(F&& f) & {
using res_t = decltype(f(value_));
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f(value_);
else
return res_t{error_};
}
template <class F>
auto and_then(F&& f) && {
using res_t = decltype(f(std::move(value_)));
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f(std::move(value_));
else
return res_t{std::move(error_)};
}
template <class F>
auto and_then(F&& f) const& {
using res_t = decltype(f(value_));
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f(value_);
else
return res_t{error_};
}
template <class F>
auto and_then(F&& f) const&& {
using res_t = decltype(f(std::move(value_)));
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f(std::move(value_));
else
return res_t{std::move(error_)};
}
template <class F>
expected or_else(F&& f) & {
using res_t = decltype(f(error_));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(error_);
return *this;
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place, value_};
else
return f(error_);
}
}
template <class F>
expected or_else(F&& f) && {
using res_t = decltype(f(std::move(error_)));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(std::move(error_));
return std::move(*this);
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place, std::move(value_)};
else
return f(std::move(error_));
}
}
template <class F>
expected or_else(F&& f) const& {
using res_t = decltype(f(error_));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(error_);
return *this;
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place, value_};
else
return f(error_);
}
}
template <class F>
expected or_else(F&& f) const&& {
using res_t = decltype(f(std::move(error_)));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(std::move(error_));
return std::move(*this);
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place, std::move(value_)};
else
return f(std::move(error_));
}
}
template <class F>
auto transform(F&& f) & {
using res_t = decltype(f(value_));
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f), value_);
else
return expected<res_t>{error_};
}
template <class F>
auto transform(F&& f) && {
using res_t = decltype(f(value_));
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f), std::move(value_));
else
return expected<res_t>{std::move(error_)};
}
template <class F>
auto transform(F&& f) const& {
using res_t = decltype(f(value_));
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f), value_);
else
return expected<res_t>{error_};
}
template <class F>
auto transform(F&& f) const&& {
using res_t = decltype(f(value_));
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f), std::move(value_));
else
return expected<res_t>{std::move(error_)};
}
template <class F>
expected transform_or(F&& f) & {
using res_t = decltype(f(error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return {std::in_place, value_};
else
return expected{f(error_)};
}
template <class F>
expected transform_or(F&& f) && {
using res_t = decltype(f(error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return {std::in_place, std::move(value_)};
else
return expected{f(std::move(error_))};
}
template <class F>
expected transform_or(F&& f) const& {
using res_t = decltype(f(error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return {std::in_place, value_};
else
return expected{f(error_)};
}
template <class F>
expected transform_or(F&& f) const&& {
using res_t = decltype(f(error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return {std::in_place, std::move(value_)};
else
return expected{f(std::move(error_))};
}
private: private:
void construct(expected&& other) noexcept(nothrow_move) { void construct(expected&& other) noexcept(nothrow_move) {
if (other.engaged_) if (other.has_value_)
new (std::addressof(value_)) T(std::move(other.value_)); new (std::addressof(value_)) T(std::move(other.value_));
else else
new (std::addressof(error_)) caf::error(std::move(other.error_)); new (std::addressof(error_)) caf::error(std::move(other.error_));
engaged_ = other.engaged_; has_value_ = other.has_value_;
} }
void construct(const expected& other) noexcept(nothrow_copy) { void construct(const expected& other) noexcept(nothrow_copy) {
if (other.engaged_) if (other.has_value_)
new (std::addressof(value_)) T(other.value_); new (std::addressof(value_)) T(other.value_);
else else
new (std::addressof(error_)) caf::error(other.error_); new (std::addressof(error_)) caf::error(other.error_);
engaged_ = other.engaged_; has_value_ = other.has_value_;
} }
void destroy() { void destroy() {
if (engaged_) if (has_value())
value_.~T(); value_.~T();
else else
error_.~error(); error_.~error();
} }
bool engaged_; /// Denotes whether `value_` may be accessed. When false, error_ may be
/// accessed.
bool has_value_;
union { union {
/// Stores the contained value.
T value_; T value_;
/// Stores an error in case there is no value.
caf::error error_; caf::error error_;
}; };
}; };
...@@ -346,46 +657,311 @@ operator!=(Enum x, const expected<T>& y) { ...@@ -346,46 +657,311 @@ operator!=(Enum x, const expected<T>& y) {
template <> template <>
class expected<void> { class expected<void> {
public: public:
expected() = default; // -- member types -----------------------------------------------------------
expected(unit_t) noexcept { using value_type = void;
// nop
} using error_type = caf::error;
template <class U>
using rebind = expected<U>;
// -- constructors, destructors, and assignment operators --------------------
expected(caf::error e) noexcept : error_(std::move(e)) { template <class Enum, class = std::enable_if_t<is_error_code_enum_v<Enum>>>
expected(Enum x) : error_(std::in_place, x) {
// nop // nop
} }
expected(const expected& other) noexcept : error_(other.error_) { expected() noexcept = default;
[[deprecated("use the default constructor instead")]] //
expected(unit_t) noexcept {
// nop // nop
} }
expected(expected&& other) noexcept : error_(std::move(other.error_)) { expected(caf::error err) noexcept : error_(std::move(err)) {
// nop // nop
} }
template <class Enum, class = std::enable_if_t<is_error_code_enum_v<Enum>>> expected(const expected& other) = default;
expected(Enum code) : error_(code) {
expected(expected&& other) noexcept = default;
explicit expected(std::in_place_t) {
// nop // nop
} }
expected& operator=(const expected& other) = default; expected& operator=(const expected& other) = default;
expected& operator=(expected&& other) noexcept { expected& operator=(expected&& other) noexcept = default;
error_ = std::move(other.error_);
expected& operator=(caf::error err) noexcept {
error_ = std::move(err);
return *this; return *this;
} }
explicit operator bool() const { template <class Enum, class = std::enable_if_t<is_error_code_enum_v<Enum>>>
expected& operator=(Enum code) {
error_ = make_error(code);
return *this;
}
// -- observers --------------------------------------------------------------
/// Returns `true` if the object does *not* hold an error.
explicit operator bool() const noexcept {
return !error_; return !error_;
} }
const caf::error& error() const { /// Returns `true` if the object does *not* hold an error.
return error_; bool has_value() const noexcept {
return !error_;
}
// -- modifiers --------------------------------------------------------------
template <class... Args>
void emplace() noexcept {
error_ = std::nullopt;
}
void swap(expected& other) noexcept {
error_.swap(other.error_);
}
// -- value access -----------------------------------------------------------
void value() const& {
// nop
}
void value() && {
// nop
}
void operator*() const noexcept {
// nop
}
// -- error access -----------------------------------------------------------
caf::error& error() & noexcept {
CAF_ASSERT(!has_value());
return *error_;
}
const caf::error& error() const& noexcept {
CAF_ASSERT(!has_value());
return *error_;
}
caf::error&& error() && noexcept {
CAF_ASSERT(!has_value());
return std::move(*error_);
}
const caf::error&& error() const&& noexcept {
CAF_ASSERT(!has_value());
return std::move(*error_);
}
// -- monadic functions ------------------------------------------------------
template <class F>
auto and_then(F&& f) & {
using res_t = decltype(f());
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f();
else
return res_t{*error_};
}
template <class F>
auto and_then(F&& f) && {
using res_t = decltype(f());
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f();
else
return res_t{std::move(*error_)};
}
template <class F>
auto and_then(F&& f) const& {
using res_t = decltype(f());
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f();
else
return res_t{*error_};
}
template <class F>
auto and_then(F&& f) const&& {
using res_t = decltype(f());
static_assert(detail::is_expected_v<res_t>, "F must return an expected");
if (has_value())
return f();
else
return res_t{std::move(*error_)};
}
template <class F>
expected or_else(F&& f) & {
using res_t = decltype(f(*error_));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(*error_);
return *this;
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place};
else
return f(*error_);
}
}
template <class F>
expected or_else(F&& f) && {
using res_t = decltype(f(std::move(*error_)));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(std::move(*error_));
return std::move(*this);
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place};
else
return f(std::move(*error_));
}
}
template <class F>
expected or_else(F&& f) const& {
using res_t = decltype(f(*error_));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(*error_);
return *this;
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place};
else
return f(*error_);
}
}
template <class F>
expected or_else(F&& f) const&& {
using res_t = decltype(f(std::move(*error_)));
if constexpr (std::is_void_v<res_t>) {
if (!has_value())
f(std::move(*error_));
return std::move(*this);
} else {
static_assert(std::is_same_v<expected, res_t>,
"F must return expected<T> or void");
if (has_value())
return expected{std::in_place};
else
return f(std::move(*error_));
}
}
template <class F>
auto transform(F&& f) & {
using res_t = decltype(f());
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f));
else
return expected<res_t>{*error_};
}
template <class F>
auto transform(F&& f) && {
using res_t = decltype(f());
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f));
else
return expected<res_t>{std::move(*error_)};
}
template <class F>
auto transform(F&& f) const& {
using res_t = decltype(f());
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f));
else
return expected<res_t>{*error_};
}
template <class F>
auto transform(F&& f) const&& {
using res_t = decltype(f());
static_assert(!detail::is_expected_v<res_t>,
"F must not return an expected");
if (has_value())
return detail::expected_from_fn(std::forward<F>(f));
else
return expected<res_t>{std::move(*error_)};
}
template <class F>
expected transform_or(F&& f) & {
using res_t = decltype(f(*error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return expected{};
else
return expected{f(*error_)};
}
template <class F>
expected transform_or(F&& f) && {
using res_t = decltype(f(std::move(*error_)));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return expected{};
else
return expected{f(std::move(*error_))};
}
template <class F>
expected transform_or(F&& f) const& {
using res_t = decltype(f(*error_));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return expected{std::in_place};
else
return expected{f(*error_)};
}
template <class F>
expected transform_or(F&& f) const&& {
using res_t = decltype(f(std::move(*error_)));
static_assert(std::is_same_v<caf::error, res_t>, "F must return an error");
if (has_value())
return expected{};
else
return expected{f(std::move(*error_))};
} }
private: private:
caf::error error_; std::optional<caf::error> error_;
}; };
/// @relates expected /// @relates expected
...@@ -399,7 +975,8 @@ inline bool operator!=(const expected<void>& x, const expected<void>& y) { ...@@ -399,7 +975,8 @@ inline bool operator!=(const expected<void>& x, const expected<void>& y) {
} }
template <> template <>
class expected<unit_t> : public expected<void> { class [[deprecated("use expected<void> instead")]] expected<unit_t>
: public expected<void> {
public: public:
using expected<void>::expected; using expected<void>::expected;
}; };
...@@ -422,8 +999,8 @@ inline std::string to_string(const expected<void>& x) { ...@@ -422,8 +999,8 @@ inline std::string to_string(const expected<void>& x) {
namespace std { namespace std {
template <class T> template <class T>
auto operator<<(ostream& oss, const caf::expected<T>& x) [[deprecated("use caf::to_string instead")]] auto
-> decltype(oss << *x) { operator<<(ostream& oss, const caf::expected<T>& x) -> decltype(oss << *x) {
if (x) if (x)
oss << *x; oss << *x;
else else
......
...@@ -52,20 +52,6 @@ private: ...@@ -52,20 +52,6 @@ private:
std::tuple<Ts...>* storage_; std::tuple<Ts...>* storage_;
}; };
template <>
class function_view_storage<unit_t> {
public:
using type = function_view_storage;
explicit function_view_storage(unit_t&) {
// nop
}
void operator()() {
// nop
}
};
struct CAF_CORE_EXPORT function_view_storage_catch_all { struct CAF_CORE_EXPORT function_view_storage_catch_all {
message* storage_; message* storage_;
...@@ -95,11 +81,6 @@ struct function_view_flattened_result<std::tuple<T>> { ...@@ -95,11 +81,6 @@ struct function_view_flattened_result<std::tuple<T>> {
using type = T; using type = T;
}; };
template <>
struct function_view_flattened_result<std::tuple<void>> {
using type = unit_t;
};
template <class T> template <class T>
using function_view_flattened_result_t = using function_view_flattened_result_t =
typename function_view_flattened_result<T>::type; typename function_view_flattened_result<T>::type;
...@@ -174,13 +155,24 @@ public: ...@@ -174,13 +155,24 @@ public:
if (!impl_) if (!impl_)
return result_type{sec::bad_function_call}; return result_type{sec::bad_function_call};
error err; error err;
function_view_result<value_type> result; if constexpr (std::is_void_v<value_type>) {
self_->request(impl_, timeout, std::forward<Ts>(xs)...) self_->request(impl_, timeout, std::forward<Ts>(xs)...)
.receive([&](error& x) { err = std::move(x); }, .receive([&](error& x) { err = std::move(x); }, [] {});
typename function_view_storage<value_type>::type{result.value}); if (err)
if (err) return result_type{err};
return result_type{err}; else
return result_type{flatten(result.value)}; return result_type{};
} else {
function_view_result<value_type> result;
self_->request(impl_, timeout, std::forward<Ts>(xs)...)
.receive([&](error& x) { err = std::move(x); },
typename function_view_storage<value_type>::type{
result.value});
if (err)
return result_type{err};
else
return result_type{flatten(result.value)};
}
} }
void assign(type x) { void assign(type x) {
......
...@@ -10,85 +10,928 @@ ...@@ -10,85 +10,928 @@
#include "caf/sec.hpp" #include "caf/sec.hpp"
#include <cassert>
using namespace caf; using namespace caf;
using namespace std::literals;
namespace { namespace {
class counted_int : public ref_counted {
public:
explicit counted_int(int initial_value) : value_(initial_value) {
// nop
}
int value() const {
return value_;
}
void value(int new_value) {
value_ = new_value;
}
private:
int value_;
};
using counted_int_ptr = intrusive_ptr<counted_int>;
using e_int = expected<int>; using e_int = expected<int>;
using e_str = expected<std::string>; using e_str = expected<std::string>;
using e_void = expected<void>;
using e_iptr = expected<counted_int_ptr>;
} // namespace } // namespace
CAF_TEST(both_engaged_equal) { TEST_CASE("expected reports its status via has_value() or operator bool()") {
e_int x{42}; e_int x{42};
e_int y{42};
CHECK(x); CHECK(x);
CHECK(y); CHECK(x.has_value());
CHECK_EQ(x, y); e_int y{sec::runtime_error};
CHECK_EQ(x, 42); CHECK(!y);
CHECK_EQ(y, 42); CHECK(!y.has_value());
}
TEST_CASE("an expected exposed its value via value()") {
auto i = make_counted<counted_int>(42);
SUBCASE("mutable lvalue access") {
auto ex = e_iptr{i};
auto val = counted_int_ptr{ex.value()}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(ex.value(), i);
e_void ev;
ev.value(); // fancy no-op
}
SUBCASE("const lvalue access") {
const auto ex = e_iptr{i};
auto val = counted_int_ptr{ex.value()}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(ex.value(), i);
const e_void ev;
ev.value(); // fancy no-op
}
SUBCASE("mutable rvalue access") {
auto ex = e_iptr{i};
auto val = counted_int_ptr{std::move(ex).value()}; // must move the value
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(ex.value(), nullptr);
e_void ev;
std::move(ev).value(); // fancy no-op
}
SUBCASE("const rvalue access") {
const auto ex = e_iptr{i};
auto val = counted_int_ptr{std::move(ex).value()}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(ex.value(), i);
const e_void ev;
std::move(ev).value(); // fancy no-op
}
#ifdef CAF_ENABLE_EXCEPTIONS
SUBCASE("value() throws if has_value() would return false") {
auto val = e_iptr{sec::runtime_error};
const auto& cval = val;
CHECK_THROWS_AS(val.value(), std::runtime_error);
CHECK_THROWS_AS(std::move(val).value(), std::runtime_error);
CHECK_THROWS_AS(cval.value(), std::runtime_error);
CHECK_THROWS_AS(std::move(cval).value(), std::runtime_error);
}
#endif
}
TEST_CASE("an expected exposed its value via operator*()") {
auto i = make_counted<counted_int>(42);
SUBCASE("mutable lvalue access") {
auto ex = e_iptr{i};
auto val = counted_int_ptr{*ex}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(*ex, i);
e_void ev;
*ev; // fancy no-op
}
SUBCASE("const lvalue access") {
const auto ex = e_iptr{i};
auto val = counted_int_ptr{*ex}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(*ex, i);
const e_void ev;
*ev; // fancy no-op
}
SUBCASE("mutable rvalue access") {
auto ex = e_iptr{i};
auto val = counted_int_ptr{*std::move(ex)}; // must move the value
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(*ex, nullptr);
e_void ev;
*std::move(ev); // fancy no-op
}
SUBCASE("const rvalue access") {
const auto ex = e_iptr{i};
auto val = counted_int_ptr{*std::move(ex)}; // must make a copy
CHECK_EQ(val, i);
if (CHECK(ex.has_value()))
CHECK_EQ(*ex, i);
const e_void ev;
*std::move(ev); // fancy no-op
}
}
TEST_CASE("an expected exposed its value via operator->()") {
SUBCASE("mutable lvalue access") {
auto val = e_str{std::in_place, "foo"};
CHECK_EQ(val->c_str(), "foo"s);
val->push_back('!');
CHECK_EQ(val->c_str(), "foo!"s);
}
SUBCASE("const lvalue access") {
const auto val = e_str{std::in_place, "foo"};
CHECK_EQ(val->c_str(), "foo"s);
using pointer_t = decltype(val.operator->());
static_assert(std::is_same_v<pointer_t, const std::string*>);
}
}
TEST_CASE("value_or() returns the stored value or a fallback") {
auto i = make_counted<counted_int>(42);
auto j = make_counted<counted_int>(24);
SUBCASE("lvalue access with value") {
auto val = e_iptr{i};
auto k = counted_int_ptr{val.value_or(j)};
CHECK_EQ(val, i);
CHECK_EQ(k, i);
}
SUBCASE("lvalue access with error") {
auto val = e_iptr{sec::runtime_error};
auto k = counted_int_ptr{val.value_or(j)};
CHECK_EQ(val, error{sec::runtime_error});
CHECK_EQ(k, j);
}
SUBCASE("rvalue access with value") {
auto val = e_iptr{i};
auto k = counted_int_ptr{std::move(val).value_or(j)};
CHECK_EQ(val, nullptr);
CHECK_EQ(k, i);
}
SUBCASE("rvalue access with error") {
auto val = e_iptr{sec::runtime_error};
auto k = counted_int_ptr{std::move(val).value_or(j)};
CHECK_EQ(val, error{sec::runtime_error});
CHECK_EQ(k, j);
}
} }
CAF_TEST(both_engaged_not_equal) { TEST_CASE("emplace destroys an old value or error and constructs a new value") {
SUBCASE("non-void value type") {
e_int x{42};
CHECK_EQ(x.value(), 42);
x.emplace(23);
CHECK_EQ(x.value(), 23);
e_int y{sec::runtime_error};
CHECK(!y);
y.emplace(23);
CHECK_EQ(y.value(), 23);
}
SUBCASE("void value type") {
e_void x;
CHECK(x.has_value());
x.emplace();
CHECK(x.has_value());
e_void y{sec::runtime_error};
CHECK(!y);
y.emplace();
CHECK(y);
}
}
TEST_CASE("swap exchanges the content of two expected") {
SUBCASE("lhs: value, rhs: value") {
e_str lhs{std::in_place, "this is value 1"};
e_str rhs{std::in_place, "this is value 2"};
CHECK_EQ(lhs, "this is value 1"s);
CHECK_EQ(rhs, "this is value 2"s);
lhs.swap(rhs);
CHECK_EQ(lhs, "this is value 2"s);
CHECK_EQ(rhs, "this is value 1"s);
}
SUBCASE("lhs: value, rhs: error") {
e_str lhs{std::in_place, "this is a value"};
e_str rhs{sec::runtime_error};
CHECK_EQ(lhs, "this is a value"s);
CHECK_EQ(rhs, error{sec::runtime_error});
lhs.swap(rhs);
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK_EQ(rhs, "this is a value"s);
}
SUBCASE("lhs: error, rhs: value") {
e_str lhs{sec::runtime_error};
e_str rhs{std::in_place, "this is a value"};
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK_EQ(rhs, "this is a value"s);
lhs.swap(rhs);
CHECK_EQ(lhs, "this is a value"s);
CHECK_EQ(rhs, error{sec::runtime_error});
}
SUBCASE("lhs: error, rhs: error") {
e_str lhs{sec::runtime_error};
e_str rhs{sec::logic_error};
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK_EQ(rhs, error{sec::logic_error});
lhs.swap(rhs);
CHECK_EQ(lhs, error{sec::logic_error});
CHECK_EQ(rhs, error{sec::runtime_error});
}
SUBCASE("lhs: void, rhs: void") {
e_void lhs;
e_void rhs;
CHECK(lhs);
CHECK(rhs);
lhs.swap(rhs); // fancy no-op
CHECK(lhs);
CHECK(rhs);
}
SUBCASE("lhs: void, rhs: error") {
e_void lhs;
e_void rhs{sec::runtime_error};
CHECK(lhs);
CHECK_EQ(rhs, error{sec::runtime_error});
lhs.swap(rhs);
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK(rhs);
}
SUBCASE("lhs: error, rhs: void") {
e_void lhs{sec::runtime_error};
e_void rhs;
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK(rhs);
lhs.swap(rhs);
CHECK(lhs);
CHECK_EQ(rhs, error{sec::runtime_error});
}
SUBCASE("lhs: error, rhs: error") {
e_void lhs{sec::runtime_error};
e_void rhs{sec::logic_error};
CHECK_EQ(lhs, error{sec::runtime_error});
CHECK_EQ(rhs, error{sec::logic_error});
lhs.swap(rhs);
CHECK_EQ(lhs, error{sec::logic_error});
CHECK_EQ(rhs, error{sec::runtime_error});
}
}
TEST_CASE("an expected can be compared to its expected type and errors") {
SUBCASE("non-void value type") {
e_int x{42};
CHECK_EQ(x, 42);
CHECK_NE(x, 24);
CHECK_NE(x, make_error(sec::runtime_error));
e_int y{sec::runtime_error};
CHECK_NE(y, 42);
CHECK_NE(y, 24);
CHECK_EQ(y, make_error(sec::runtime_error));
CHECK_NE(y, make_error(sec::logic_error));
}
SUBCASE("void value type") {
e_void x;
CHECK(x);
e_void y{sec::runtime_error};
CHECK_EQ(y, make_error(sec::runtime_error));
CHECK_NE(y, make_error(sec::logic_error));
}
}
TEST_CASE("two expected with the same value are equal") {
SUBCASE("non-void value type") {
e_int x{42};
e_int y{42};
CHECK_EQ(x, y);
CHECK_EQ(y, x);
}
SUBCASE("void value type") {
e_void x;
e_void y;
CHECK_EQ(x, y);
CHECK_EQ(y, x);
}
}
TEST_CASE("two expected with different values are unequal") {
e_int x{42}; e_int x{42};
e_int y{24}; e_int y{24};
CHECK(x);
CHECK(y);
CHECK_NE(x, y); CHECK_NE(x, y);
CHECK_NE(x, sec::unexpected_message); CHECK_NE(y, x);
CHECK_NE(y, sec::unexpected_message);
CHECK_EQ(x, 42);
CHECK_EQ(y, 24);
} }
CAF_TEST(engaged_plus_not_engaged) { TEST_CASE("an expected with value is not equal to an expected with an error") {
e_int x{42}; SUBCASE("non-void value type") {
e_int y{sec::unexpected_message}; // Use the same "underlying value" for both objects.
CHECK(x); e_int x{static_cast<int>(sec::runtime_error)};
CHECK(!y); e_int y{sec::runtime_error};
CHECK_EQ(x, 42); CHECK_NE(x, y);
CHECK_EQ(y, sec::unexpected_message); CHECK_NE(y, x);
CHECK_NE(x, sec::unexpected_message); }
CHECK_NE(x, y); SUBCASE("void value type") {
CHECK_NE(y, 42); e_void x;
CHECK_NE(y, sec::unsupported_sys_key); e_void y{sec::runtime_error};
CHECK_NE(x, y);
CHECK_NE(y, x);
}
} }
CAF_TEST(both_not_engaged) { TEST_CASE("two expected with the same error are equal") {
e_int x{sec::unexpected_message}; SUBCASE("non-void value type") {
e_int y{sec::unexpected_message}; e_int x{sec::runtime_error};
CHECK(!x); e_int y{sec::runtime_error};
CHECK(!y); CHECK_EQ(x, y);
CHECK_EQ(x, y); CHECK_EQ(y, x);
CHECK_EQ(x, sec::unexpected_message); }
CHECK_EQ(y, sec::unexpected_message); SUBCASE("void value type") {
CHECK_EQ(x.error(), y.error()); e_void x{sec::runtime_error};
CHECK_NE(x, sec::unsupported_sys_key); e_void y{sec::runtime_error};
CHECK_NE(y, sec::unsupported_sys_key); CHECK_EQ(x, y);
} CHECK_EQ(y, x);
}
CAF_TEST(move_and_copy) { }
e_str x{sec::unexpected_message};
e_str y{"hello"}; TEST_CASE("two expected with different errors are unequal") {
x = "hello"; SUBCASE("non-void value type") {
CHECK_NE(x, sec::unexpected_message); e_int x{sec::logic_error};
CHECK_EQ(x, "hello"); e_int y{sec::runtime_error};
CHECK_EQ(x, y); CHECK_NE(x, y);
y = "world"; CHECK_NE(y, x);
x = std::move(y); }
CHECK_EQ(x, "world"); SUBCASE("void value type") {
e_str z{std::move(x)}; e_void x{sec::logic_error};
CHECK_EQ(z, "world"); e_void y{sec::runtime_error};
e_str z_cpy{z}; CHECK_NE(x, y);
CHECK_EQ(z_cpy, "world"); CHECK_NE(y, x);
CHECK_EQ(z, z_cpy); }
z = e_str{sec::unsupported_sys_key}; }
CHECK_NE(z, z_cpy);
CHECK_EQ(z, sec::unsupported_sys_key); TEST_CASE("expected is copyable") {
} SUBCASE("non-void value type") {
SUBCASE("copy-constructible") {
CAF_TEST(construction_with_none) { e_int x{42};
e_int y{x};
CHECK_EQ(x, y);
}
SUBCASE("copy-assignable") {
e_int x{42};
e_int y{0};
CHECK_NE(x, y);
y = x;
CHECK_EQ(x, y);
}
}
SUBCASE("void value type") {
SUBCASE("copy-constructible") {
e_void x;
e_void y{x};
CHECK_EQ(x, y);
}
SUBCASE("copy-assignable") {
e_void x;
e_void y;
CHECK_EQ(x, y);
y = x;
CHECK_EQ(x, y);
}
}
}
TEST_CASE("expected is movable") {
SUBCASE("non-void value type") {
SUBCASE("move-constructible") {
auto iptr = make_counted<counted_int>(42);
CHECK_EQ(iptr->get_reference_count(), 1u);
e_iptr x{std::in_place, iptr};
e_iptr y{std::move(x)};
CHECK_EQ(iptr->get_reference_count(), 2u);
CHECK_NE(x, iptr);
CHECK_EQ(y, iptr);
}
SUBCASE("move-assignable") {
auto iptr = make_counted<counted_int>(42);
CHECK_EQ(iptr->get_reference_count(), 1u);
e_iptr x{std::in_place, iptr};
e_iptr y{std::in_place, nullptr};
CHECK_EQ(x, iptr);
CHECK_NE(y, iptr);
y = std::move(x);
CHECK_EQ(iptr->get_reference_count(), 2u);
CHECK_NE(x, iptr);
CHECK_EQ(y, iptr);
}
}
SUBCASE("non-void value type") {
SUBCASE("move-constructible") {
e_void x;
e_void y{std::move(x)};
CHECK_EQ(x, y);
}
SUBCASE("move-assignable") {
e_void x;
e_void y;
CHECK_EQ(x, y);
y = std::move(x);
CHECK_EQ(x, y);
}
}
}
TEST_CASE("expected is convertible from none") {
e_int x{none}; e_int x{none};
CHECK(!x); if (CHECK(!x))
CHECK(!x.error()); CHECK(!x.error());
e_void y{none};
if (CHECK(!y))
CHECK(!y.error());
}
TEST_CASE("and_then composes a chain of functions returning an expected") {
SUBCASE("non-void value type") {
auto inc = [](counted_int_ptr ptr) {
ptr->value(ptr->value() + 1);
return e_iptr{std::move(ptr)};
};
SUBCASE("and_then makes copies when called on a mutable lvalue") {
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = v1.and_then(inc);
CHECK_EQ(v1, i);
CHECK_EQ(v2, i);
CHECK_EQ(i->value(), 2);
}
SUBCASE("and_then makes copies when called on a const lvalue") {
auto i = make_counted<counted_int>(1);
const auto v1 = e_iptr{std::in_place, i};
const auto v2 = v1.and_then(inc);
CHECK_EQ(v1, i);
CHECK_EQ(v2, i);
CHECK_EQ(i->value(), 2);
}
SUBCASE("and_then moves when called on a mutable rvalue") {
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = std::move(v1).and_then(inc);
CHECK_EQ(v1, nullptr);
CHECK_EQ(v2, i);
CHECK_EQ(i->value(), 2);
}
SUBCASE("and_then makes copies when called on a const rvalue") {
auto i = make_counted<counted_int>(1);
const auto v1 = e_iptr{std::in_place, i};
const auto v2 = std::move(v1).and_then(inc);
CHECK_EQ(v1, i);
CHECK_EQ(v2, i);
CHECK_EQ(i->value(), 2);
}
}
SUBCASE("void value type") {
auto called = false;
auto fn = [&called] {
called = true;
return e_void{};
};
SUBCASE("mutable lvalue") {
called = false;
auto v1 = e_void{};
auto v2 = v1.and_then(fn);
CHECK(called);
CHECK_EQ(v1, v2);
}
SUBCASE("const lvalue") {
called = false;
const auto v1 = e_void{};
const auto v2 = v1.and_then(fn);
CHECK(called);
CHECK_EQ(v1, v2);
}
SUBCASE("mutable rvalue") {
called = false;
auto v1 = e_void{};
auto v2 = std::move(v1).and_then(fn);
CHECK(called);
CHECK_EQ(v1, v2);
}
SUBCASE("const rvalue") {
called = false;
const auto v1 = e_void{};
const auto v2 = std::move(v1).and_then(fn);
CHECK(called);
CHECK_EQ(v1, v2);
}
}
}
TEST_CASE("and_then does nothing when called with an error") {
SUBCASE("non-void value type") {
auto inc = [](counted_int_ptr ptr) {
ptr->value(ptr->value() + 1);
return e_iptr{std::move(ptr)};
};
auto v1 = e_iptr{sec::runtime_error};
auto v2 = v1.and_then(inc); // mutable lvalue
const auto v3 = std::move(v2).and_then(inc); // mutable rvalue
const auto v4 = v3.and_then(inc); // const lvalue
auto v5 = std::move(v4).and_then(inc); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
}
SUBCASE("void value type") {
auto fn = [] { return e_void{}; };
auto v1 = e_void{sec::runtime_error};
auto v2 = v1.and_then(fn); // mutable lvalue
const auto v3 = std::move(v2).and_then(fn); // mutable rvalue
const auto v4 = v3.and_then(fn); // const lvalue
auto v5 = std::move(v4).and_then(fn); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
}
}
TEST_CASE("transform applies a function to change the value") {
SUBCASE("non-void value type") {
auto inc = [](counted_int_ptr ptr) {
return make_counted<counted_int>(ptr->value() + 1);
};
SUBCASE("transform makes copies when called on a mutable lvalue") {
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = v1.transform(inc);
CHECK_EQ(i->value(), 1);
CHECK_EQ(v1, i);
if (CHECK(v2))
CHECK_EQ((*v2)->value(), 2);
}
SUBCASE("transform makes copies when called on a const lvalue") {
auto i = make_counted<counted_int>(1);
const auto v1 = e_iptr{std::in_place, i};
const auto v2 = v1.transform(inc);
CHECK_EQ(i->value(), 1);
CHECK_EQ(v1, i);
if (CHECK(v2))
CHECK_EQ((*v2)->value(), 2);
}
SUBCASE("transform moves when called on a mutable rvalue") {
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = std::move(v1).transform(inc);
CHECK_EQ(i->value(), 1);
CHECK_EQ(v1, nullptr);
if (CHECK(v2))
CHECK_EQ((*v2)->value(), 2);
}
SUBCASE("transform makes copies when called on a const rvalue") {
auto i = make_counted<counted_int>(1);
const auto v1 = e_iptr{std::in_place, i};
const auto v2 = std::move(v1).transform(inc);
CHECK_EQ(i->value(), 1);
CHECK_EQ(v1, i);
if (CHECK(v2))
CHECK_EQ((*v2)->value(), 2);
}
}
SUBCASE("void value type") {
auto fn = [] { return 42; };
SUBCASE("mutable lvalue") {
auto v1 = e_void{};
auto v2 = v1.transform(fn);
CHECK_EQ(v2, 42);
}
SUBCASE("const lvalue") {
const auto v1 = e_void{};
const auto v2 = v1.transform(fn);
CHECK_EQ(v2, 42);
}
SUBCASE("mutable rvalue") {
auto v1 = e_void{};
auto v2 = std::move(v1).transform(fn);
CHECK_EQ(v2, 42);
}
SUBCASE("const rvalue") {
const auto v1 = e_void{};
const auto v2 = std::move(v1).transform(fn);
CHECK_EQ(v2, 42);
}
}
}
TEST_CASE("transform does nothing when called with an error") {
SUBCASE("non-void value type") {
auto inc = [](counted_int_ptr ptr) {
return make_counted<counted_int>(ptr->value() + 1);
};
auto v1 = e_iptr{sec::runtime_error};
auto v2 = v1.transform(inc); // mutable lvalue
const auto v3 = std::move(v2).transform(inc); // mutable rvalue
const auto v4 = v3.transform(inc); // const lvalue
auto v5 = std::move(v4).transform(inc); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
}
SUBCASE("void value type") {
auto fn = [] {};
auto v1 = e_void{sec::runtime_error};
auto v2 = v1.transform(fn); // mutable lvalue
const auto v3 = std::move(v2).transform(fn); // mutable rvalue
const auto v4 = v3.transform(fn); // const lvalue
auto v5 = std::move(v4).transform(fn); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
}
}
namespace {
template <class T, bool WrapIntoExpected>
struct next_error_t {
auto operator()(const error& err) const {
assert(err.category() == type_id_v<sec>);
auto code = err.code() + 1;
if constexpr (WrapIntoExpected)
return expected<T>{make_error(static_cast<sec>(code))};
else
return make_error(static_cast<sec>(code));
}
auto operator()(error&& err) const {
assert(err.category() == type_id_v<sec>);
auto code = err.code() + 1;
err = make_error(static_cast<sec>(code));
if constexpr (WrapIntoExpected)
return expected<T>{std::move(err)};
else
return std::move(err);
}
};
} // namespace
TEST_CASE("or_else may replace the error or set a default") {
SUBCASE("non-void value type") {
next_error_t<int, true> next_error;
auto set_fallback = [](auto&& err) {
if constexpr (std::is_same_v<decltype(err), error&&>) {
// Just for testing that we get indeed an rvalue in the test.
err = error{};
}
return e_int{42};
};
SUBCASE("or_else makes copies when called on a mutable lvalue") {
auto v1 = e_int{sec::runtime_error};
auto v2 = v1.or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
auto v3 = v2.or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK_EQ(v3, 42);
}
SUBCASE("or_else makes copies when called on a const lvalue") {
const auto v1 = e_int{sec::runtime_error};
const auto v2 = v1.or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
const auto v3 = v2.or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK_EQ(v3, 42);
}
SUBCASE("or_else moves when called on a mutable rvalue") {
auto v1 = e_int{sec::runtime_error};
auto v2 = std::move(v1).or_else(next_error);
CHECK_EQ(v1, error{});
CHECK_EQ(v2, sec::remote_linking_failed);
auto v3 = std::move(v2).or_else(set_fallback);
CHECK_EQ(v2, error{});
CHECK_EQ(v3, 42);
}
SUBCASE("or_else makes copies when called on a const rvalue") {
const auto v1 = e_int{sec::runtime_error};
const auto v2 = std::move(v1).or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
const auto v3 = std::move(v2).or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK_EQ(v3, 42);
}
}
SUBCASE("void value type") {
next_error_t<void, true> next_error;
auto set_fallback = [](auto&& err) {
if constexpr (std::is_same_v<decltype(err), error&&>) {
// Just for testing that we get indeed an rvalue in the test.
err = error{};
}
return e_void{};
};
SUBCASE("or_else makes copies when called on a mutable lvalue") {
auto v1 = e_void{sec::runtime_error};
auto v2 = v1.or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
auto v3 = v2.or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK(v3);
}
SUBCASE("or_else makes copies when called on a const lvalue") {
const auto v1 = e_void{sec::runtime_error};
const auto v2 = v1.or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
const auto v3 = v2.or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK(v3);
}
SUBCASE("or_else moves when called on a mutable rvalue") {
auto v1 = e_void{sec::runtime_error};
auto v2 = std::move(v1).or_else(next_error);
CHECK_EQ(v1, error{});
CHECK_EQ(v2, sec::remote_linking_failed);
auto v3 = std::move(v2).or_else(set_fallback);
CHECK_EQ(v2, error{});
CHECK(v3);
}
SUBCASE("or_else makes copies when called on a const rvalue") {
const auto v1 = e_void{sec::runtime_error};
const auto v2 = std::move(v1).or_else(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
const auto v3 = std::move(v2).or_else(set_fallback);
CHECK_EQ(v2, sec::remote_linking_failed);
CHECK(v3);
}
}
}
TEST_CASE("or_else leaves the expected unchanged when returning void") {
SUBCASE("non-void value type") {
auto i = 0;
auto inc = [&i](auto&&) { ++i; };
auto v1 = e_int{sec::runtime_error};
auto v2 = v1.or_else(inc); // mutable lvalue
const auto v3 = std::move(v2).or_else(inc); // mutable rvalue
const auto v4 = v3.or_else(inc); // const lvalue
auto v5 = std::move(v4).or_else(inc); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
CHECK_EQ(i, 4);
}
SUBCASE("void value type") {
auto i = 0;
auto inc = [&i](auto&&) { ++i; };
auto v1 = e_void{sec::runtime_error};
auto v2 = v1.or_else(inc); // mutable lvalue
const auto v3 = std::move(v2).or_else(inc); // mutable rvalue
const auto v4 = v3.or_else(inc); // const lvalue
auto v5 = std::move(v4).or_else(inc); // const rvalue
CHECK_EQ(v1, error{sec::runtime_error});
CHECK_EQ(v2, error{}); // has been moved-from
CHECK_EQ(v3, error{sec::runtime_error});
CHECK_EQ(v4, error{sec::runtime_error});
CHECK_EQ(v5, error{sec::runtime_error});
CHECK_EQ(i, 4);
}
}
TEST_CASE("or_else does nothing when called with a value") {
SUBCASE("non-void value type") {
auto uh_oh = [](auto&&) { FAIL("uh-oh called!"); };
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = v1.or_else(uh_oh); // mutable lvalue
const auto v3 = std::move(v2).or_else(uh_oh); // mutable rvalue
const auto v4 = v3.or_else(uh_oh); // const lvalue
auto v5 = std::move(v4).or_else(uh_oh); // const rvalue
CHECK_EQ(v1, i);
CHECK_EQ(v2, nullptr); // has been moved-from
CHECK_EQ(v3, i);
CHECK_EQ(v4, i);
CHECK_EQ(v5, i);
}
SUBCASE("void value type") {
auto uh_oh = [](auto&&) { FAIL("uh-oh called!"); };
auto v1 = e_void{};
auto v2 = v1.or_else(uh_oh); // mutable lvalue
const auto v3 = std::move(v2).or_else(uh_oh); // mutable rvalue
const auto v4 = v3.or_else(uh_oh); // const lvalue
auto v5 = std::move(v4).or_else(uh_oh); // const rvalue
CHECK(v1);
CHECK(v2);
CHECK(v3);
CHECK(v4);
CHECK(v5);
}
}
TEST_CASE("transform_or may replace the error or set a default") {
SUBCASE("non-void value type") {
next_error_t<int, false> next_error;
SUBCASE("transform_or makes copies when called on a mutable lvalue") {
auto v1 = e_int{sec::runtime_error};
auto v2 = v1.transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or makes copies when called on a const lvalue") {
const auto v1 = e_int{sec::runtime_error};
const auto v2 = v1.transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or moves when called on a mutable rvalue") {
auto v1 = e_int{sec::runtime_error};
auto v2 = std::move(v1).transform_or(next_error);
CHECK_EQ(v1, error{});
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or makes copies when called on a const rvalue") {
const auto v1 = e_int{sec::runtime_error};
const auto v2 = std::move(v1).transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
}
SUBCASE("void value type") {
next_error_t<void, false> next_error;
SUBCASE("transform_or makes copies when called on a mutable lvalue") {
auto v1 = e_void{sec::runtime_error};
auto v2 = v1.transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or makes copies when called on a const lvalue") {
const auto v1 = e_void{sec::runtime_error};
const auto v2 = v1.transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or moves when called on a mutable rvalue") {
auto v1 = e_void{sec::runtime_error};
auto v2 = std::move(v1).transform_or(next_error);
CHECK_EQ(v1, error{});
CHECK_EQ(v2, sec::remote_linking_failed);
}
SUBCASE("transform_or makes copies when called on a const rvalue") {
const auto v1 = e_void{sec::runtime_error};
const auto v2 = std::move(v1).transform_or(next_error);
CHECK_EQ(v1, sec::runtime_error);
CHECK_EQ(v2, sec::remote_linking_failed);
}
}
}
TEST_CASE("transform_or does nothing when called with a value") {
SUBCASE("non-void value type") {
auto uh_oh = [](auto&&) {
FAIL("uh-oh called!");
return error{};
};
auto i = make_counted<counted_int>(1);
auto v1 = e_iptr{std::in_place, i};
auto v2 = v1.transform_or(uh_oh); // mutable lvalue
const auto v3 = std::move(v2).transform_or(uh_oh); // mutable rvalue
const auto v4 = v3.transform_or(uh_oh); // const lvalue
auto v5 = std::move(v4).transform_or(uh_oh); // const rvalue
CHECK_EQ(v1, i);
CHECK_EQ(v2, nullptr); // has been moved-from
CHECK_EQ(v3, i);
CHECK_EQ(v4, i);
CHECK_EQ(v5, i);
}
SUBCASE("void value type") {
auto uh_oh = [](auto&&) {
FAIL("uh-oh called!");
return error{};
};
auto v1 = e_void{};
auto v2 = v1.transform_or(uh_oh); // mutable lvalue
const auto v3 = std::move(v2).transform_or(uh_oh); // mutable rvalue
const auto v4 = v3.transform_or(uh_oh); // const lvalue
auto v5 = std::move(v4).transform_or(uh_oh); // const rvalue
CHECK(v1);
CHECK(v2);
CHECK(v3);
CHECK(v4);
CHECK(v5);
}
} }
...@@ -788,12 +788,12 @@ expected<void> read_port(native_socket fd, SockAddrType& sa) { ...@@ -788,12 +788,12 @@ expected<void> read_port(native_socket fd, SockAddrType& sa) {
socket_size_type len = sizeof(SockAddrType); socket_size_type len = sizeof(SockAddrType);
CALL_CFUN(res, detail::cc_zero, "getsockname", CALL_CFUN(res, detail::cc_zero, "getsockname",
getsockname(fd, reinterpret_cast<sockaddr*>(&sa), &len)); getsockname(fd, reinterpret_cast<sockaddr*>(&sa), &len));
return unit; return {};
} }
expected<void> set_inaddr_any(native_socket, sockaddr_in& sa) { expected<void> set_inaddr_any(native_socket, sockaddr_in& sa) {
sa.sin_addr.s_addr = INADDR_ANY; sa.sin_addr.s_addr = INADDR_ANY;
return unit; return {};
} }
expected<void> set_inaddr_any(native_socket fd, sockaddr_in6& sa) { expected<void> set_inaddr_any(native_socket fd, sockaddr_in6& sa) {
...@@ -804,7 +804,7 @@ expected<void> set_inaddr_any(native_socket fd, sockaddr_in6& sa) { ...@@ -804,7 +804,7 @@ expected<void> set_inaddr_any(native_socket fd, sockaddr_in6& sa) {
setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY,
reinterpret_cast<setsockopt_ptr>(&off), reinterpret_cast<setsockopt_ptr>(&off),
static_cast<socket_size_type>(sizeof(off)))); static_cast<socket_size_type>(sizeof(off))));
return unit; return {};
} }
template <int Family, int SockType = SOCK_STREAM> template <int Family, int SockType = SOCK_STREAM>
......
...@@ -128,7 +128,7 @@ expected<void> child_process_inherit(native_socket fd, bool new_value) { ...@@ -128,7 +128,7 @@ expected<void> child_process_inherit(native_socket fd, bool new_value) {
// calculate and set new flags // calculate and set new flags
auto wf = (!new_value) ? (rf | FD_CLOEXEC) : (rf & (~(FD_CLOEXEC))); auto wf = (!new_value) ? (rf | FD_CLOEXEC) : (rf & (~(FD_CLOEXEC)));
CALL_CFUN(set_res, detail::cc_not_minus1, "fcntl", fcntl(fd, F_SETFD, wf)); CALL_CFUN(set_res, detail::cc_not_minus1, "fcntl", fcntl(fd, F_SETFD, wf));
return unit; return {};
} }
expected<void> keepalive(native_socket fd, bool new_value) { expected<void> keepalive(native_socket fd, bool new_value) {
...@@ -137,7 +137,7 @@ expected<void> keepalive(native_socket fd, bool new_value) { ...@@ -137,7 +137,7 @@ expected<void> keepalive(native_socket fd, bool new_value) {
CALL_CFUN(res, detail::cc_zero, "setsockopt", CALL_CFUN(res, detail::cc_zero, "setsockopt",
setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value, setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &value,
static_cast<unsigned>(sizeof(value)))); static_cast<unsigned>(sizeof(value))));
return unit; return {};
} }
expected<void> nonblocking(native_socket fd, bool new_value) { expected<void> nonblocking(native_socket fd, bool new_value) {
...@@ -147,7 +147,7 @@ expected<void> nonblocking(native_socket fd, bool new_value) { ...@@ -147,7 +147,7 @@ expected<void> nonblocking(native_socket fd, bool new_value) {
// calculate and set new flags // calculate and set new flags
auto wf = new_value ? (rf | O_NONBLOCK) : (rf & (~(O_NONBLOCK))); auto wf = new_value ? (rf | O_NONBLOCK) : (rf & (~(O_NONBLOCK)));
CALL_CFUN(set_res, detail::cc_not_minus1, "fcntl", fcntl(fd, F_SETFL, wf)); CALL_CFUN(set_res, detail::cc_not_minus1, "fcntl", fcntl(fd, F_SETFL, wf));
return unit; return {};
} }
expected<void> allow_sigpipe(native_socket fd, bool new_value) { expected<void> allow_sigpipe(native_socket fd, bool new_value) {
...@@ -157,12 +157,12 @@ expected<void> allow_sigpipe(native_socket fd, bool new_value) { ...@@ -157,12 +157,12 @@ expected<void> allow_sigpipe(native_socket fd, bool new_value) {
setsockopt(fd, SOL_SOCKET, no_sigpipe_socket_flag, &value, setsockopt(fd, SOL_SOCKET, no_sigpipe_socket_flag, &value,
static_cast<unsigned>(sizeof(value)))); static_cast<unsigned>(sizeof(value))));
} }
return unit; return {};
} }
expected<void> allow_udp_connreset(native_socket, bool) { expected<void> allow_udp_connreset(native_socket, bool) {
// nop; SIO_UDP_CONNRESET only exists on Windows // nop; SIO_UDP_CONNRESET only exists on Windows
return unit; return {};
} }
std::pair<native_socket, native_socket> create_pipe() { std::pair<native_socket, native_socket> create_pipe() {
...@@ -353,7 +353,7 @@ expected<void> send_buffer_size(native_socket fd, int new_value) { ...@@ -353,7 +353,7 @@ expected<void> send_buffer_size(native_socket fd, int new_value) {
setsockopt(fd, SOL_SOCKET, SO_SNDBUF, setsockopt(fd, SOL_SOCKET, SO_SNDBUF,
reinterpret_cast<setsockopt_ptr>(&new_value), reinterpret_cast<setsockopt_ptr>(&new_value),
static_cast<socket_size_type>(sizeof(int)))); static_cast<socket_size_type>(sizeof(int))));
return unit; return {};
} }
expected<void> tcp_nodelay(native_socket fd, bool new_value) { expected<void> tcp_nodelay(native_socket fd, bool new_value) {
...@@ -363,7 +363,7 @@ expected<void> tcp_nodelay(native_socket fd, bool new_value) { ...@@ -363,7 +363,7 @@ expected<void> tcp_nodelay(native_socket fd, bool new_value) {
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, setsockopt(fd, IPPROTO_TCP, TCP_NODELAY,
reinterpret_cast<setsockopt_ptr>(&flag), reinterpret_cast<setsockopt_ptr>(&flag),
static_cast<socket_size_type>(sizeof(flag)))); static_cast<socket_size_type>(sizeof(flag))));
return unit; return {};
} }
bool is_error(signed_size_type res, bool is_nonblock) { bool is_error(signed_size_type res, bool is_nonblock) {
......
...@@ -23,6 +23,10 @@ ...@@ -23,6 +23,10 @@
} \ } \
void CAF_UNIQUE(test)::run_test_impl() void CAF_UNIQUE(test)::run_test_impl()
#define SUBCASE(description) \
CAF_MESSAGE(description); \
if (true)
#define GIVEN(description) \ #define GIVEN(description) \
CAF_MESSAGE("GIVEN " description); \ CAF_MESSAGE("GIVEN " description); \
if (true) if (true)
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment