1
1
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:
Nerijus Arlauskas 2016-09-22 11:10:09 +03:00 committed by GitHub
commit 56dc9c9630
7 changed files with 480 additions and 10 deletions

@ -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;