mirror of
https://github.com/trafi/maybe-result-cpp
synced 2024-11-22 02:32:02 +01:00
Merge pull request #1 from trafi/feature/partial-specialization-for-ok-void
Implement partial specialization for result<void, E>.
This commit is contained in:
commit
56dc9c9630
@ -11,5 +11,9 @@
|
||||
#pragma once
|
||||
|
||||
namespace maybe {
|
||||
template <typename T, typename E> class result;
|
||||
template <typename T, typename E>
|
||||
class result;
|
||||
|
||||
template <typename E>
|
||||
class result<void, E>;
|
||||
}
|
@ -274,8 +274,15 @@ namespace maybe {
|
||||
* @param f F(T) -> U
|
||||
* @return maybe::result<U, E>
|
||||
*/
|
||||
template <typename F>
|
||||
inline auto map(F f) noexcept -> maybe::result<typename std::result_of<F(T)>::type, E>;
|
||||
template <typename F, typename R = typename std::result_of<F(T)>::type>
|
||||
inline auto map(F f) noexcept -> maybe::result<R, E>;
|
||||
|
||||
/**
|
||||
* Maps a result<T, E> to result<void, E>, leaving an err value untouched.
|
||||
*
|
||||
* @return maybe::result<void, E>
|
||||
*/
|
||||
inline auto map_void() noexcept -> maybe::result<void, E>;
|
||||
|
||||
/**
|
||||
* Maps a result<T, E> to result<U, E> by always returning provided U value on success,
|
||||
@ -350,6 +357,269 @@ namespace maybe {
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
class result<void, E> final {
|
||||
private:
|
||||
std::experimental::optional<E> var_err;
|
||||
|
||||
public:
|
||||
typedef void ok_type;
|
||||
typedef E err_type;
|
||||
|
||||
result()
|
||||
{
|
||||
}
|
||||
|
||||
result(internal::placeholder)
|
||||
{
|
||||
}
|
||||
|
||||
result(internal::placeholder, E&& value) : var_err(std::forward<E>(value))
|
||||
{
|
||||
}
|
||||
|
||||
result(internal::placeholder, const E& value) : var_err(value)
|
||||
{
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ok value.
|
||||
*
|
||||
* @param void value
|
||||
* @return result<void, E>
|
||||
*/
|
||||
constexpr static result<void, E> ok() noexcept
|
||||
{
|
||||
return std::forward<result<void, E>>(result<void, E>(internal::placeholder{}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new err value.
|
||||
*
|
||||
* @param E value
|
||||
* @return result<void, E>
|
||||
*/
|
||||
constexpr static result<void, E> err(E&& value) noexcept
|
||||
{
|
||||
return std::forward<result<void, E>>(
|
||||
result<void, E>(internal::placeholder{}, std::forward<E>(value)));
|
||||
}
|
||||
|
||||
constexpr static result<void, E> err(const E& value) noexcept
|
||||
{
|
||||
return std::forward<result<void, E>>(result<void, E>(internal::placeholder{}, value));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new ok value using `void()` constructor.
|
||||
*
|
||||
* @return result<void, E>
|
||||
*/
|
||||
constexpr static result<void, E> default_ok() noexcept
|
||||
{
|
||||
return std::experimental::constexpr_move(
|
||||
result<void, E>(void(), internal::placeholder{}));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new err value using `E()` constructor.
|
||||
*
|
||||
* @return result<void, E>
|
||||
*/
|
||||
constexpr static result<void, E> default_err() noexcept
|
||||
{
|
||||
return std::experimental::constexpr_move(result<void, E>(internal::placeholder{}, E()));
|
||||
}
|
||||
|
||||
// Inspection.
|
||||
|
||||
/**
|
||||
* Check if result contains ok value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
inline bool is_ok() const noexcept
|
||||
{
|
||||
return !var_err;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if result contains err value.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
inline bool is_err() const noexcept
|
||||
{
|
||||
return !!var_err;
|
||||
}
|
||||
|
||||
explicit inline operator bool() const noexcept
|
||||
{
|
||||
return is_ok();
|
||||
}
|
||||
|
||||
void ok_value()
|
||||
{
|
||||
if (is_err()) {
|
||||
throw std::experimental::bad_optional_access("bad optional access");
|
||||
}
|
||||
}
|
||||
|
||||
#if OPTIONAL_HAS_MOVE_ACCESSORS == 1
|
||||
|
||||
constexpr E const& err_value() const&
|
||||
{
|
||||
return var_err.value();
|
||||
}
|
||||
|
||||
OPTIONAL_MUTABLE_CONSTEXPR E& err_value() &
|
||||
{
|
||||
return var_err.value();
|
||||
}
|
||||
|
||||
OPTIONAL_MUTABLE_CONSTEXPR E&& err_value() &&
|
||||
{
|
||||
return std::move(var_err.value());
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Retrieve err value or throw `bad_optional_access` exception.
|
||||
*
|
||||
* @return E
|
||||
*/
|
||||
E const& err_value() const
|
||||
{
|
||||
return var_err.value();
|
||||
}
|
||||
|
||||
E& err_value()
|
||||
{
|
||||
return var_err.value();
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
#if OPTIONAL_HAS_THIS_RVALUE_REFS == 1
|
||||
|
||||
template <class V>
|
||||
constexpr E err_value_or(V&& v) const&
|
||||
{
|
||||
return var_err.value_or(std::forward<V>(v));
|
||||
}
|
||||
|
||||
template <class V>
|
||||
OPTIONAL_MUTABLE_CONSTEXPR E err_value_or(V&& v) &&
|
||||
{
|
||||
return var_err.value_or(std::forward<V>(v));
|
||||
}
|
||||
|
||||
#else
|
||||
|
||||
/**
|
||||
* Retrieve err value or the provided default `V` which can be casted to `E`.
|
||||
*
|
||||
* @return E
|
||||
*/
|
||||
template <class V>
|
||||
E err_value_or(V&& v) const
|
||||
{
|
||||
return var_err.value_or(std::forward<V>(v));
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
// Functional helpers.
|
||||
|
||||
/**
|
||||
* Maps a result<void, E> to result<U, E> (where U is return value of F(void)) by applying a
|
||||
* function F to a
|
||||
* contained ok value, leaving an err value untouched.
|
||||
*
|
||||
* This function can be used to compose the results of two functions.
|
||||
*
|
||||
* @param f F() -> U
|
||||
* @return maybe::result<U, E>
|
||||
*/
|
||||
template <typename F>
|
||||
inline auto map(F f) noexcept -> maybe::result<typename std::result_of<F()>::type, E>;
|
||||
|
||||
/**
|
||||
* Maps a result<void, E> to result<U, E> by always returning provided U value on success,
|
||||
* leaving an err value untouched.
|
||||
*
|
||||
* This function can be used to compose the results of two functions.
|
||||
*
|
||||
* @param value U
|
||||
* @return maybe::result<U, E>
|
||||
*/
|
||||
template <typename U>
|
||||
inline auto map_value(U value) noexcept -> maybe::result<U, E>;
|
||||
|
||||
/**
|
||||
* Maps a result<void, E> to result<void, U> (where U is return value of F(E)) by applying a
|
||||
* function to a
|
||||
* function to a
|
||||
* contained err value, leaving an ok value untouched.
|
||||
*
|
||||
* This function can be used to pass through a successful result while changing an error.
|
||||
*
|
||||
* @param f F(E) -> U
|
||||
* @return maybe::result<void, U>
|
||||
*/
|
||||
template <typename F>
|
||||
inline auto map_err(F f) noexcept
|
||||
-> maybe::result<void, typename std::result_of<F(E)>::type>;
|
||||
|
||||
/**
|
||||
* Maps a result<void, E> to result<void, U> by always returning provided U value on error,
|
||||
* leaving an ok value untouched.
|
||||
*
|
||||
* This function can be used to compose the results of two functions.
|
||||
*
|
||||
* @param value U
|
||||
* @return maybe::result<void, U>
|
||||
*/
|
||||
template <typename U>
|
||||
inline auto map_err_value(U value) noexcept -> maybe::result<void, U>;
|
||||
|
||||
/**
|
||||
* Calls op if the result is ok, otherwise returns the err value of self.
|
||||
*
|
||||
* This function can be used for control flow based on result values.
|
||||
*
|
||||
* @param f F() -> maybe::result<U, E>
|
||||
* @return maybe::result<U, E>
|
||||
*/
|
||||
template <typename F>
|
||||
inline auto and_then(F op) noexcept -> typename std::result_of<F()>::type;
|
||||
|
||||
/**
|
||||
* Converts into another result with different ok type `R` and forwards the same error.
|
||||
*
|
||||
* The ok type `R` must have `R()` constructor in case the result does not contain an err.
|
||||
*
|
||||
* @return maybe::result<R, E>
|
||||
*/
|
||||
template <typename R>
|
||||
inline auto into_err() noexcept -> R;
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
constexpr bool operator==(const result<void, E>& x, const result<void, E>& y)
|
||||
{
|
||||
return x.is_ok() && y.is_ok()
|
||||
? true
|
||||
: (x.is_err() && y.is_err() ? x.err_value() == y.err_value() : false);
|
||||
}
|
||||
|
||||
template <typename E>
|
||||
constexpr bool operator!=(const result<void, E>& x, const result<void, E>& y)
|
||||
{
|
||||
return !(x == y);
|
||||
}
|
||||
}
|
||||
|
||||
#include "result.inline.hpp"
|
@ -13,15 +13,24 @@
|
||||
#include "result.hpp"
|
||||
|
||||
template <typename T, typename E>
|
||||
template <typename F>
|
||||
inline auto maybe::result<T, E>::map(F f) noexcept -> maybe::result<typename std::result_of<F(T)>::type, E>
|
||||
template <typename F, typename R>
|
||||
inline auto maybe::result<T, E>::map(F f) noexcept -> maybe::result<R, E>
|
||||
{
|
||||
typedef maybe::result<typename std::result_of<F(T)>::type, E> return_result_t;
|
||||
|
||||
if (is_err()) {
|
||||
return return_result_t::err(std::forward<E>(err_value()));
|
||||
return maybe::result<R, E>::err(std::forward<E>(err_value()));
|
||||
}
|
||||
return return_result_t::ok(f(ok_value()));
|
||||
|
||||
return maybe::result<R, E>::ok(f(ok_value()));
|
||||
};
|
||||
|
||||
template <typename T, typename E>
|
||||
inline auto maybe::result<T, E>::map_void() noexcept -> maybe::result<void, E>
|
||||
{
|
||||
if (is_err()) {
|
||||
return maybe::result<void, E>::err(std::forward<E>(err_value()));
|
||||
}
|
||||
|
||||
return maybe::result<void, E>::ok();
|
||||
};
|
||||
|
||||
template <typename T, typename E>
|
||||
@ -79,4 +88,74 @@ inline auto maybe::result<T, E>::into_err() noexcept -> R
|
||||
return R::err(std::forward<E>(err_value()));
|
||||
}
|
||||
return R::default_ok();
|
||||
};
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename F>
|
||||
inline auto maybe::result<void, E>::map(F f) noexcept
|
||||
-> maybe::result<typename std::result_of<F()>::type, E>
|
||||
{
|
||||
typedef maybe::result<typename std::result_of<F()>::type, E> return_result_t;
|
||||
|
||||
if (is_err()) {
|
||||
return return_result_t::err(std::forward<E>(err_value()));
|
||||
}
|
||||
return return_result_t::ok(f());
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename U>
|
||||
inline auto maybe::result<void, E>::map_value(U value) noexcept -> maybe::result<U, E>
|
||||
{
|
||||
if (is_err()) {
|
||||
return maybe::result<U, E>::err(std::forward<E>(err_value()));
|
||||
}
|
||||
|
||||
return maybe::result<U, E>::ok(std::forward<U>(value));
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename F>
|
||||
inline auto maybe::result<void, E>::map_err(F f) noexcept
|
||||
-> maybe::result<void, typename std::result_of<F(E)>::type>
|
||||
{
|
||||
typedef maybe::result<void, typename std::result_of<F(E)>::type> return_result_t;
|
||||
|
||||
if (is_ok()) {
|
||||
return return_result_t::ok();
|
||||
}
|
||||
return return_result_t::err(f(err_value()));
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename U>
|
||||
inline auto maybe::result<void, E>::map_err_value(U value) noexcept -> maybe::result<void, U>
|
||||
{
|
||||
if (is_ok()) {
|
||||
return maybe::result<void, U>::ok();
|
||||
}
|
||||
|
||||
return maybe::result<void, U>::err(std::forward<U>(value));
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename F>
|
||||
inline auto maybe::result<void, E>::and_then(F f) noexcept -> typename std::result_of<F()>::type
|
||||
{
|
||||
typedef typename std::result_of<F()>::type result_t;
|
||||
|
||||
if (is_err()) {
|
||||
return maybe::result<typename result_t::ok_type, E>::err(std::forward<E>(err_value()));
|
||||
}
|
||||
return f();
|
||||
};
|
||||
|
||||
template <typename E>
|
||||
template <typename R>
|
||||
inline auto maybe::result<void, E>::into_err() noexcept -> R
|
||||
{
|
||||
if (is_err()) {
|
||||
return R::err(std::forward<E>(err_value()));
|
||||
}
|
||||
return R::default_ok();
|
||||
};
|
||||
|
@ -30,6 +30,21 @@ TEST_CASE("result_and_then")
|
||||
REQUIRE(b.ok_value().value == "hello world");
|
||||
}
|
||||
|
||||
SECTION("chains another function returning different void result if previous one was successful")
|
||||
{
|
||||
auto a = result<A, int>::ok(A("hello"));
|
||||
auto b = a.and_then([](A v) { return result<void, int>::ok(); });
|
||||
REQUIRE(b);
|
||||
}
|
||||
|
||||
SECTION("chains another function returning different result if previous one was successful void")
|
||||
{
|
||||
auto a = result<void, int>::ok();
|
||||
auto b = a.and_then([]() { return result<B, int>::ok(B("Void world")); });
|
||||
REQUIRE(b);
|
||||
REQUIRE(b.ok_value().value == "Void world");
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"should not run another function returning different result if previous one returned error")
|
||||
{
|
||||
@ -38,4 +53,22 @@ TEST_CASE("result_and_then")
|
||||
REQUIRE(!b);
|
||||
REQUIRE(43 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"should not run another function returning different void result if previous one returned error")
|
||||
{
|
||||
auto a = result<A, int>::err(43);
|
||||
auto b = a.and_then([](A v) { return result<void, int>::ok(); });
|
||||
REQUIRE(!b);
|
||||
REQUIRE(43 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION(
|
||||
"should not run another function returning different result if previous void one returned error")
|
||||
{
|
||||
auto a = result<void, int>::err(43);
|
||||
auto b = a.and_then([]() { return result<B, int>::ok(B("Void world")); });
|
||||
REQUIRE(!b);
|
||||
REQUIRE(43 == b.err_value());
|
||||
}
|
||||
}
|
@ -30,6 +30,14 @@ TEST_CASE("result_map_err")
|
||||
REQUIRE(42 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION("converts err bool to err int for void function")
|
||||
{
|
||||
auto a = result<void, bool>::err(true);
|
||||
auto b = a.map_err([](bool v) { return v ? 42 : 43; });
|
||||
REQUIRE(!b);
|
||||
REQUIRE(42 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION("does not convert anything and returns ok if not err")
|
||||
{
|
||||
auto a = result<A, bool>::ok(A("hi"));
|
||||
@ -38,6 +46,13 @@ TEST_CASE("result_map_err")
|
||||
REQUIRE(b.ok_value().value == "hi");
|
||||
}
|
||||
|
||||
SECTION("does not convert anything and returns ok if not err for void ok type")
|
||||
{
|
||||
auto a = result<void, bool>::ok();
|
||||
auto b = a.map_err([](bool v) { return v ? 42 : 43; });
|
||||
REQUIRE(b);
|
||||
}
|
||||
|
||||
SECTION("replaces err A with err B")
|
||||
{
|
||||
auto a = result<int, A>::err(A("hello"));
|
||||
@ -46,6 +61,14 @@ TEST_CASE("result_map_err")
|
||||
REQUIRE(b.err_value().value == "bye");
|
||||
}
|
||||
|
||||
SECTION("replaces err A with err B for void ok type")
|
||||
{
|
||||
auto a = result<void, A>::err(A("hello"));
|
||||
auto b = a.map_err_value(B("bye"));
|
||||
REQUIRE(!b);
|
||||
REQUIRE(b.err_value().value == "bye");
|
||||
}
|
||||
|
||||
SECTION("does not replace err A with err B if A is ok")
|
||||
{
|
||||
auto a = result<int, A>::ok(43);
|
||||
@ -53,4 +76,11 @@ TEST_CASE("result_map_err")
|
||||
REQUIRE(b);
|
||||
REQUIRE(43 == b.ok_value());
|
||||
}
|
||||
|
||||
SECTION("does not replace err A with err B if A is void ok")
|
||||
{
|
||||
auto a = result<void, A>::ok();
|
||||
auto b = a.map_err_value(B("bye"));
|
||||
REQUIRE(b);
|
||||
}
|
||||
}
|
@ -30,6 +30,14 @@ TEST_CASE("result_map")
|
||||
REQUIRE(b.ok_value().value == "hello world");
|
||||
}
|
||||
|
||||
SECTION("converts result void to result B")
|
||||
{
|
||||
auto a = result<void, int>::ok();
|
||||
auto b = a.map([]() { return B("Void world"); });
|
||||
REQUIRE(b);
|
||||
REQUIRE(b.ok_value().value == "Void world");
|
||||
}
|
||||
|
||||
SECTION("does not convert result A to result B if value was error")
|
||||
{
|
||||
auto a = result<A, int>::err(43);
|
||||
@ -38,6 +46,14 @@ TEST_CASE("result_map")
|
||||
REQUIRE(43 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION("does not convert result A to result void if value was error")
|
||||
{
|
||||
auto a = result<A, int>::err(43);
|
||||
auto b = a.map_void();
|
||||
REQUIRE(!b);
|
||||
REQUIRE(43 == b.err_value());
|
||||
}
|
||||
|
||||
SECTION("replaces result A with result B")
|
||||
{
|
||||
auto a = result<A, int>::ok(A("hello"));
|
||||
|
@ -71,6 +71,21 @@ TEST_CASE("result")
|
||||
REQUIRE(res != other_different_err);
|
||||
}
|
||||
|
||||
SECTION("created err result returns err value in result<void, E>")
|
||||
{
|
||||
auto res = result<void, int>::err(12);
|
||||
REQUIRE(res.is_err());
|
||||
REQUIRE(!res.is_ok());
|
||||
REQUIRE(!res);
|
||||
REQUIRE(12 == res.err_value());
|
||||
|
||||
auto other_err = result<void, int>::err(12);
|
||||
REQUIRE(res == other_err);
|
||||
|
||||
auto other_different_err = result<void, int>::err(42);
|
||||
REQUIRE(res != other_different_err);
|
||||
}
|
||||
|
||||
SECTION("created ok not equal created result")
|
||||
{
|
||||
auto ok = result<int, int>::ok(12);
|
||||
@ -78,6 +93,13 @@ TEST_CASE("result")
|
||||
REQUIRE(ok != err);
|
||||
}
|
||||
|
||||
SECTION("created ok not equal created result in result<void, E>")
|
||||
{
|
||||
auto ok = result<void, int>::ok();
|
||||
auto err = result<void, int>::err(12);
|
||||
REQUIRE(ok != err);
|
||||
}
|
||||
|
||||
SECTION("throws exception if invalid value accessed")
|
||||
{
|
||||
auto ok = result<int, int>::ok(12);
|
||||
@ -86,6 +108,14 @@ TEST_CASE("result")
|
||||
REQUIRE_THROWS(err.ok_value());
|
||||
}
|
||||
|
||||
SECTION("throws exception if invalid value accessed in result<void, E>")
|
||||
{
|
||||
auto ok = result<void, int>::ok();
|
||||
auto err = result<void, int>::err(12);
|
||||
REQUIRE_THROWS(ok.err_value());
|
||||
REQUIRE_THROWS(err.ok_value());
|
||||
}
|
||||
|
||||
SECTION("returns default values")
|
||||
{
|
||||
auto ok = result<int, int>::ok(12);
|
||||
@ -96,6 +126,14 @@ TEST_CASE("result")
|
||||
REQUIRE(err.ok_value_or(42) == 42);
|
||||
}
|
||||
|
||||
SECTION("returns default values in result<void, E>")
|
||||
{
|
||||
auto ok = result<void, int>::ok();
|
||||
auto err = result<void, int>::err(12);
|
||||
REQUIRE(err.err_value_or(42) == 12);
|
||||
REQUIRE(ok.err_value_or(42) == 42);
|
||||
}
|
||||
|
||||
SECTION("destroys ok value correctly")
|
||||
{
|
||||
std::ostringstream ss;
|
||||
|
Loading…
Reference in New Issue
Block a user