1
1
Fork 0
mirror of https://github.com/trafi/maybe-result-cpp synced 2024-05-08 23:16:11 +02:00

Move accessors, additional tests and docs.

This commit is contained in:
Nerijus Arlauskas 2016-07-08 13:32:08 +03:00
parent 89c1bc061b
commit b6e242b36c
9 changed files with 402 additions and 84 deletions

View File

@ -13,6 +13,11 @@ AlignAfterOpenBracket: true
AlwaysBreakAfterDefinitionReturnType: None
AlwaysBreakTemplateDeclarations: true
IndentCaseLabels: true
PenaltyReturnTypeOnItsOwnLine: 300
PenaltyBreakBeforeFirstCallParameter: 1
PenaltyBreakComment: 300
PenaltyBreakFirstLessLess: 120
PenaltyBreakString: 1000
PenaltyExcessCharacter: 1000000
PenaltyReturnTypeOnItsOwnLine: 200
NamespaceIndentation: All
Standard: Cpp11

68
src/maybe/platforms.hpp Normal file
View File

@ -0,0 +1,68 @@
#pragma once
/**
* Figure out waht features to enable based on compiler versions.
*
* This logic is re-used from std::experimental::optional.
*/
# if defined __GNUC__ // NOTE: GNUC is also defined for Clang
# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 8)
# define MAYBE_RESULT_GCC_4_8_AND_HIGHER___
# elif (__GNUC__ > 4)
# define MAYBE_RESULT_GCC_4_8_AND_HIGHER___
# endif
#
# if (__GNUC__ == 4) && (__GNUC_MINOR__ >= 7)
# define MAYBE_RESULT_GCC_4_7_AND_HIGHER___
# elif (__GNUC__ > 4)
# define MAYBE_RESULT_GCC_4_7_AND_HIGHER___
# endif
#
# if (__GNUC__ == 4) && (__GNUC_MINOR__ == 8) && (__GNUC_PATCHLEVEL__ >= 1)
# define MAYBE_RESULT_GCC_4_8_1_AND_HIGHER___
# elif (__GNUC__ == 4) && (__GNUC_MINOR__ >= 9)
# define MAYBE_RESULT_GCC_4_8_1_AND_HIGHER___
# elif (__GNUC__ > 4)
# define MAYBE_RESULT_GCC_4_8_1_AND_HIGHER___
# endif
# endif
#
# if defined __clang_major__
# if (__clang_major__ == 3 && __clang_minor__ >= 5)
# define MAYBE_RESULT_CLANG_3_5_AND_HIGHTER_
# elif (__clang_major__ > 3)
# define MAYBE_RESULT_CLANG_3_5_AND_HIGHTER_
# endif
# if defined MAYBE_RESULT_CLANG_3_5_AND_HIGHTER_
# define MAYBE_RESULT_CLANG_3_4_2_AND_HIGHER_
# elif (__clang_major__ == 3 && __clang_minor__ == 4 && __clang_patchlevel__ >= 2)
# define MAYBE_RESULT_CLANG_3_4_2_AND_HIGHER_
# endif
# endif
# if defined __clang__
# if (__clang_major__ > 2) || (__clang_major__ == 2) && (__clang_minor__ >= 9)
# define MAYBE_RESULT_HAS_THIS_RVALUE_REFS 1
# else
# define MAYBE_RESULT_HAS_THIS_RVALUE_REFS 0
# endif
# elif defined MAYBE_RESULT_GCC_4_8_1_AND_HIGHER___
# define MAYBE_RESULT_HAS_THIS_RVALUE_REFS 1
# else
# define MAYBE_RESULT_HAS_THIS_RVALUE_REFS 0
# endif
# if defined MAYBE_RESULT_CLANG_3_5_AND_HIGHTER_ && (defined __cplusplus) && (__cplusplus != 201103L)
# define MAYBE_RESULT_HAS_MOVE_ACCESSORS 1
# else
# define MAYBE_RESULT_HAS_MOVE_ACCESSORS 0
# endif
# // In C++11 constexpr implies const, so we need to make non-const members also non-constexpr
# if (defined __cplusplus) && (__cplusplus == 201103L)
# define MAYBE_RESULT_MUTABLE_CONSTEXPR
# else
# define MAYBE_RESULT_MUTABLE_CONSTEXPR constexpr
# endif

View File

@ -10,9 +10,11 @@
#pragma once
#include <cassert>
#include <stdexcept>
#include <string>
#include "platforms.hpp"
#include "result.fwd.hpp"
namespace maybe {
@ -35,51 +37,77 @@ namespace maybe {
// workaround: std utility functions aren't constexpr yet
template <class T>
inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type& t) noexcept
inline constexpr T&& mr_constexpr_forward(typename std::remove_reference<T>::type& t) noexcept
{
return static_cast<T&&>(t);
}
template <class T>
inline constexpr T&& constexpr_forward(typename std::remove_reference<T>::type&& t) noexcept
inline constexpr T&& mr_constexpr_forward(typename std::remove_reference<T>::type&& t) noexcept
{
static_assert(!std::is_lvalue_reference<T>::value, "!!");
return static_cast<T&&>(t);
}
template <class T>
inline constexpr typename std::remove_reference<T>::type&& mr_constexpr_move(T&& t) noexcept
{
return static_cast<typename std::remove_reference<T>::type&&>(t);
}
template <typename T, typename E>
class result final {
private:
constexpr void copy_from(const result<T, E>& other) noexcept;
constexpr void set_from(result<T, E>& other) noexcept;
constexpr void init_ok(T&& value) noexcept;
constexpr void init_ok(const T& value) noexcept;
constexpr void init_err(E&& value) noexcept;
constexpr void init_err(const E& value) noexcept;
constexpr void clear() noexcept;
internal::Value tag;
union {
T ok_val;
E err_val;
};
public:
typedef T ok_type;
typedef T err_type;
result() : tag(internal::Value::NONE)
{
}
/** ok initializer */
// Internal initialization.
/** Ok initializer */
result(T&& value, internal::Placeholder) : tag(internal::Value::OK)
{
init_ok(std::forward<T>(value));
}
/** ok initializer */
/** Ok initializer */
result(const T& value, internal::Placeholder) : tag(internal::Value::OK)
{
init_ok(value);
}
/** err initializer */
/** Err initializer */
result(internal::Placeholder, E&& value) : tag(internal::Value::ERR)
{
init_err(std::forward<E>(value));
}
/** err initializer */
/** Err initializer */
result(internal::Placeholder, const E& value) : tag(internal::Value::ERR)
{
init_err(value);
}
// Set up and tear down.
/** copy constructor */
result(const result& other) noexcept
{
@ -114,42 +142,196 @@ namespace maybe {
clear();
};
// Static construction helpers.
/** helper constructor for default ok value */
constexpr static result<T, E> default_ok() noexcept
{
return result<T, E>(T(), internal::Placeholder{});
return mr_constexpr_move(result<T, E>(T(), internal::Placeholder{}));
}
/** helper constructor for ok value */
constexpr static result<T, E> ok(T&& value) noexcept
{
return result<T, E>(std::forward<T>(value), internal::Placeholder{});
return mr_constexpr_move(result<T, E>(std::forward<T>(value), internal::Placeholder{}));
}
/** helper constructor for ok value */
constexpr static result<T, E> ok(const T& value) noexcept
{
return result<T, E>(value, internal::Placeholder{});
return mr_constexpr_move(result<T, E>(value, internal::Placeholder{}));
}
/** helper constructor for default err value */
constexpr static result<T, E> default_err() noexcept
{
return result<T, E>(internal::Placeholder{}, E());
return mr_constexpr_move(result<T, E>(internal::Placeholder{}, E()));
}
/** helper constructor for err value */
constexpr static result<T, E> err(E&& err) noexcept
{
return result<T, E>(internal::Placeholder{}, std::forward<E>(err));
return mr_constexpr_move(result<T, E>(internal::Placeholder{}, std::forward<E>(err)));
}
/** helper constructor for err value */
constexpr static result<T, E> err(const E& err) noexcept
{
return result<T, E>(internal::Placeholder{}, err);
return mr_constexpr_move(result<T, E>(internal::Placeholder{}, err));
}
#if MAYBE_RESULT_HAS_MOVE_ACCESSORS == 1
MAYBE_RESULT_MUTABLE_CONSTEXPR T* operator->()
{
return ok_dataptr();
}
MAYBE_RESULT_MUTABLE_CONSTEXPR T& operator*() &
{
return *ok_dataptr();
}
MAYBE_RESULT_MUTABLE_CONSTEXPR T&& operator*() &&
{
return mr_constexpr_move(*ok_dataptr());
}
constexpr T const& ok_value() const&
{
return is_ok() ? ok_val : (throw bad_result_access("bad ok result access"), ok_val);
}
MAYBE_RESULT_MUTABLE_CONSTEXPR T& ok_value() &
{
return is_ok() ? ok_val : (throw bad_result_access("bad ok result access"), ok_val);
}
MAYBE_RESULT_MUTABLE_CONSTEXPR T&& ok_value() &&
{
if (!is_ok())
throw bad_result_access("bad ok result access");
return std::move(ok_val);
}
constexpr E const& err_value() const&
{
return is_err() ? err_val : (throw bad_result_access("bad err result access"), err_val);
}
MAYBE_RESULT_MUTABLE_CONSTEXPR E& err_value() &
{
return is_err() ? err_val : (throw bad_result_access("bad err result access"), err_val);
}
MAYBE_RESULT_MUTABLE_CONSTEXPR E&& err_value() &&
{
if (!is_err())
throw bad_result_access("bad err result access");
return std::move(err_val);
}
#else
T* operator->()
{
return ok_dataptr();
}
T& operator*()
{
return *ok_dataptr();
}
constexpr T const& ok_value() const
{
return is_ok() ? ok_val : (throw bad_result_access("bad ok result access"), ok_val);
}
T& ok_value()
{
return is_ok() ? ok_val : (throw bad_optional_access("bad ok result access"), ok_val);
}
constexpr E const& err_value() const
{
return is_err() ? err_val : (throw bad_result_access("bad err result access"), err_val);
}
E& err_value()
{
return is_err() ? err_val
: (throw bad_optional_access("bad err result access"), err_val);
}
#endif
#if MAYBE_RESULT_HAS_THIS_RVALUE_REFS == 1
template <class V>
constexpr T ok_value_or(V&& v) const&
{
return is_ok() ? ok_val : static_cast<T>(mr_constexpr_forward<V>(v));
}
template <class V>
constexpr E err_value_or(V&& v) const&
{
return is_err() ? err_val : static_cast<E>(mr_constexpr_forward<V>(v));
}
#if MAYBE_RESULT_HAS_MOVE_ACCESSORS == 1
template <class V>
MAYBE_RESULT_MUTABLE_CONSTEXPR T ok_value_or(V&& v) &&
{
return is_ok() ? mr_constexpr_move(const_cast<result<T, E>&>(*this).ok_val)
: static_cast<T>(mr_constexpr_forward<V>(v));
}
template <class V>
MAYBE_RESULT_MUTABLE_CONSTEXPR E err_value_or(V&& v) &&
{
return is_err() ? mr_constexpr_move(const_cast<result<T, E>&>(*this).err_val)
: static_cast<E>(mr_constexpr_forward<V>(v));
}
#else
template <class V>
T ok_value_or(V&& v) &&
{
return is_ok() ? mr_constexpr_move(const_cast<result<T, E>&>(*this).ok_val)
: static_cast<T>(mr_constexpr_forward<V>(v));
}
template <class V>
E err_value_or(V&& v) &&
{
return is_err() ? mr_constexpr_move(const_cast<result<T, E>&>(*this).err_val)
: static_cast<E>(mr_constexpr_forward<V>(v));
}
#endif
#else
template <class V>
constexpr T ok_value_or(V&& v) const
{
return is_ok() ? ok_val : static_cast<T>(mr_constexpr_forward<V>(v));
}
template <class V>
constexpr E err_value_or(V&& v) const
{
return is_err() ? err_val : static_cast<T>(mr_constexpr_forward<V>(v));
}
#endif
// Inspection.
constexpr bool is_ok() const noexcept
{
return tag == internal::Value::OK;
@ -160,53 +342,27 @@ namespace maybe {
return tag == internal::Value::ERR;
}
constexpr T const& ok_value() const&
{
return is_ok() ? ok_val : (throw bad_result_access("bad ok result access"), ok_val);
}
constexpr T& ok_value() &
{
return is_ok() ? ok_val : (throw bad_result_access("bad ok result access"), ok_val);
}
constexpr E const& err_value() const&
{
return is_err() ? err_val : (throw bad_result_access("bad err result access"), err_val);
}
constexpr E& err_value() &
{
return is_err() ? err_val : (throw bad_result_access("bad err result access"), err_val);
}
template <class V>
constexpr T ok_value_or(V&& v) const&
{
return is_ok() ? ok_val : static_cast<T>(constexpr_forward<V>(v));
}
template <class V>
constexpr E err_value_or(V&& v) const&
{
return is_err() ? err_val : static_cast<E>(constexpr_forward<V>(v));
}
explicit constexpr operator bool() const noexcept
{
return is_ok();
}
// Unsafe access.
constexpr T* ok_dataptr() const noexcept
{
assert(is_ok());
return std::addressof(ok_val);
}
constexpr E* err_dataptr() const noexcept
{
assert(is_err());
return std::addressof(err_val);
}
// Functional helpers.
/**
* Maps a result<T, E> to result<U, E> (where U is return value of F(T)) by applying a
* function F to a
@ -220,6 +376,15 @@ namespace maybe {
template <typename F>
constexpr auto map(F f) noexcept -> maybe::result<typename std::result_of<F(T)>::type, E>;
/**
* Maps a result<T, 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>
constexpr auto map_value(U value) noexcept -> maybe::result<U, E>;
@ -237,6 +402,18 @@ namespace maybe {
constexpr auto map_err(F f) noexcept
-> maybe::result<T, typename std::result_of<F(E)>::type>;
/**
* Maps a result<T, E> to result<T, 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<T, U>
*/
template <typename U>
constexpr auto map_err_value(U value) noexcept -> maybe::result<T, U>;
/**
* Calls op if the result is ok, otherwise returns the err value of self.
*
@ -249,28 +426,7 @@ namespace maybe {
constexpr auto and_then(F op) noexcept -> typename std::result_of<F(T)>::type;
template <typename R>
constexpr auto into_err() noexcept -> R
{
if (is_err()) {
return R::err(std::forward<E>(err_value()));
}
return R::default_ok();
};
private:
constexpr void copy_from(const result<T, E>& other) noexcept;
constexpr void set_from(result<T, E>& other) noexcept;
constexpr void init_ok(T&& value) noexcept;
constexpr void init_ok(const T& value) noexcept;
constexpr void init_err(E&& value) noexcept;
constexpr void init_err(const E& value) noexcept;
constexpr void clear() noexcept;
internal::Value tag;
union {
T ok_val;
E err_val;
};
constexpr auto into_err() noexcept -> R;
};
template <typename T, typename E>

View File

@ -133,6 +133,17 @@ constexpr auto maybe::result<T, E>::map_err(F f) noexcept
return return_result_t::err(f(std::forward<E>(err_value())));
};
template <typename T, typename E>
template <typename U>
constexpr auto maybe::result<T, E>::map_err_value(U value) noexcept -> maybe::result<T, U>
{
if (is_ok()) {
return maybe::result<T, U>::ok(std::forward<T>(ok_value()));
}
return maybe::result<T, U>::err(std::forward<U>(value));
};
template <typename T, typename E>
template <typename F>
constexpr auto maybe::result<T, E>::and_then(F f) noexcept -> typename std::result_of<F(T)>::type
@ -143,4 +154,14 @@ constexpr auto maybe::result<T, E>::and_then(F f) noexcept -> typename std::resu
return maybe::result<typename result_t::ok_type, E>::err(std::forward<E>(err_value()));
}
return f(std::forward<T>(ok_value()));
};
template <typename T, typename E>
template <typename R>
constexpr auto maybe::result<T, E>::into_err() noexcept -> R
{
if (is_err()) {
return R::err(std::forward<E>(err_value()));
}
return R::default_ok();
};

View File

@ -61,11 +61,11 @@ TEST_CASE("example")
auto success_result = run(true);
REQUIRE(success_result);
REQUIRE(success_result.ok_value() == 4);
REQUIRE(4 == success_result.ok_value());
auto error_result = run(false);
REQUIRE(!error_result);
REQUIRE(error_result.err_value() == "file not found");
REQUIRE("file not found" == error_result.err_value());
}
}

View File

@ -36,6 +36,6 @@ TEST_CASE("result_and_then")
auto a = result<A, int>::err(43);
auto b = a.and_then([](A v) { return result<B, int>::ok(B(v.value + " world")); });
REQUIRE(!b);
REQUIRE(b.err_value() == 43);
REQUIRE(43 == b.err_value());
}
}

View File

@ -10,6 +10,14 @@ public:
std::string value;
};
class B final {
public:
B(std::string value) : value(value)
{
}
std::string value;
};
using maybe::result;
TEST_CASE("result_map_err")
@ -19,7 +27,7 @@ TEST_CASE("result_map_err")
auto a = result<A, bool>::err(true);
auto b = a.map_err([](bool v) { return v ? 42 : 43; });
REQUIRE(!b);
REQUIRE(b.err_value() == 42);
REQUIRE(42 == b.err_value());
}
SECTION("does not convert anything and returns ok if not err")
@ -29,4 +37,20 @@ TEST_CASE("result_map_err")
REQUIRE(b);
REQUIRE(b.ok_value().value == "hi");
}
SECTION("replaces err A with err B")
{
auto a = result<int, 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);
auto b = a.map_err_value(B("bye"));
REQUIRE(b);
REQUIRE(43 == b.ok_value());
}
}

View File

@ -35,6 +35,22 @@ TEST_CASE("result_map")
auto a = result<A, int>::err(43);
auto b = a.map([](A v) { return B(v.value = " world"); });
REQUIRE(!b);
REQUIRE(b.err_value() == 43);
REQUIRE(43 == b.err_value());
}
SECTION("replaces result A with result B")
{
auto a = result<A, int>::ok(A("hello"));
auto b = a.map_value(B("bye"));
REQUIRE(b);
REQUIRE(b.ok_value().value == "bye");
}
SECTION("does not replace result A with result B if A is err")
{
auto a = result<A, int>::err(43);
auto b = a.map_value(B("bye"));
REQUIRE(!b);
REQUIRE(43 == b.err_value());
}
}

View File

@ -1,9 +1,6 @@
#include "catch.hpp"
#include <functional>
#include <maybe/result.hpp>
#include <memory>
#include <sstream>
using maybe::result;
@ -46,7 +43,7 @@ TEST_CASE("result")
REQUIRE(res.is_ok());
REQUIRE(!res.is_err());
REQUIRE(res);
REQUIRE(res.ok_value() == 12);
REQUIRE(12 == res.ok_value());
auto other_ok = result<int, int>::ok(12);
REQUIRE(res == other_ok);
@ -61,7 +58,7 @@ TEST_CASE("result")
REQUIRE(res.is_err());
REQUIRE(!res.is_ok());
REQUIRE(!res);
REQUIRE(res.err_value() == 12);
REQUIRE(12 == res.err_value());
auto other_err = result<int, int>::err(12);
REQUIRE(res == other_err);
@ -77,13 +74,13 @@ TEST_CASE("result")
REQUIRE(ok != err);
}
SECTION("throws exception if invalid value accessed")
{
auto ok = result<int, int>::ok(12);
auto err = result<int, int>::err(12);
REQUIRE_THROWS(ok.err_value());
REQUIRE_THROWS(err.ok_value());
}
SECTION("throws exception if invalid value accessed")
{
auto ok = result<int, int>::ok(12);
auto err = result<int, int>::err(12);
REQUIRE_THROWS(ok.err_value());
REQUIRE_THROWS(err.ok_value());
}
SECTION("returns default values")
{
@ -194,4 +191,35 @@ TEST_CASE("result")
REQUIRE(ss.str() == "[err]");
}
#if MAYBE_RESULT_HAS_MOVE_ACCESSORS == 1
SECTION("ok move accessor works")
{
std::ostringstream ss;
{
auto val = result<NoCopy, NoCopy>::ok(NoCopy(42, [&] { ss << "[ok]"; }));
auto&& other = val.ok_value();
REQUIRE(42 == other.get_flag());
}
REQUIRE(ss.str() == "[ok]");
}
SECTION("err move accessor works")
{
std::ostringstream ss;
{
auto val = result<NoCopy, NoCopy>::err(NoCopy(42, [&] { ss << "[err]"; }));
auto&& other = val.err_value();
REQUIRE(42 == other.get_flag());
}
REQUIRE(ss.str() == "[err]");
}
#endif
}