diff --git a/README.md b/README.md index 05f2195..50c1fcc 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,10 @@ # Maybe Result +[![Build Status](https://travis-ci.org/trafi/maybe-result-cpp.svg?branch=master)](https://travis-ci.org/trafi/maybe-result-cpp) + Maybe Result is a return value wrapper that can contain either a value `T` or error `E`. It borrows ideas heavily from the [C++17's -`std::experimental::optional`][optional], [Rust's std::result](result) and +`std::experimental::optional`][optional], [Rust's std::result][result] and the [`std::expected`][expected] that was proposed but not yet accepted for C++17. @@ -99,17 +101,32 @@ so that the include would be: A C++ compiler shat supports C++14 is required. You can use `-std=c++14` flag for sufficiently recent versions of -`GCC` or `CLANG`. +`GCC` (4.9) or `CLANG` (3.7). __Warning! Library is highly experimental and is not guaranteed to work.__ ## Running tests +Library requires `std::experimental::optional` implementation, location +of which can be specified with `-DEXPERIMENTAL_OPTIONAL_INCLUDE` flag: + ```bash -cmake . +cmake -DEXPERIMENTAL_OPTIONAL_INCLUDE=../path/to/optional . make tests && ./tests/tests ``` +There is a script that does this automatically: + +```bash +./dev/run-tests.sh +``` + +In addition to this, you can run tests on all supported compilers using docker: + +```bash +./dev/docker-run-tests.sh +``` + ## License Licensed under either of diff --git a/dev/docker-run-tests.sh b/dev/docker-run-tests.sh new file mode 100755 index 0000000..d90527a --- /dev/null +++ b/dev/docker-run-tests.sh @@ -0,0 +1,20 @@ +#!/usr/bin/env bash + +COMMAND="./dev/run-tests.sh" + +compilers=( "clang-3.7" "clang-3.8" "gcc-4.9" "gcc-5.2" "gcc-6.1" ) + +for compiler in "${compilers[@]}" +do + + echo "Env ${compiler}" + + docker run -v `pwd`:/opt -w=/opt nercury/cmake-cpp:${compiler} /bin/bash -c "${COMMAND}" + if [ $? -ne 0 ]; then + echo "Tests failed on ${compiler}" + exit 1 + fi + +done + +echo "OK!" \ No newline at end of file diff --git a/dev/run-tests.sh b/dev/run-tests.sh new file mode 100755 index 0000000..75f8c8e --- /dev/null +++ b/dev/run-tests.sh @@ -0,0 +1,14 @@ +#!/usr/bin/env bash + +SRC=`pwd` + +rm -rf /tmp/build +mkdir -p /tmp/build +cd /tmp/build +if [ ! -d "optional" ]; then + mkdir -p optional + curl -Ls https://api.github.com/repos/akrzemi1/Optional/tarball | tar --strip-components=1 -xz -C optional +fi +cmake -DEXPERIMENTAL_OPTIONAL_INCLUDE=/tmp/build/optional ${SRC} + +make -j8 tests && ./tests/tests \ No newline at end of file diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt index efa0e4e..be581b9 100644 --- a/src/CMakeLists.txt +++ b/src/CMakeLists.txt @@ -2,6 +2,9 @@ set(TARGET "maybe_result") add_library(${TARGET} INTERFACE) +SET(EXPERIMENTAL_OPTIONAL_INCLUDE "" CACHE STRING "Path to experimental optional include directory.") + target_include_directories(${TARGET} INTERFACE . + INTERFACE ${EXPERIMENTAL_OPTIONAL_INCLUDE} ) \ No newline at end of file diff --git a/src/maybe/platforms.hpp b/src/maybe/platforms.hpp deleted file mode 100644 index 7f1fa01..0000000 --- a/src/maybe/platforms.hpp +++ /dev/null @@ -1,68 +0,0 @@ -#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 \ No newline at end of file diff --git a/src/maybe/result.hpp b/src/maybe/result.hpp index 9a5e859..3902fef 100644 --- a/src/maybe/result.hpp +++ b/src/maybe/result.hpp @@ -10,45 +10,17 @@ #pragma once -#include -#include -#include - -#include "platforms.hpp" #include "result.fwd.hpp" +#include +#include + namespace maybe { - namespace internal { - enum class Value { OK, ERR, NONE }; - struct Placeholder { + struct placeholder { }; } - class bad_result_access : public std::logic_error { - public: - explicit bad_result_access(const std::string& what_arg) : logic_error{what_arg} - { - } - explicit bad_result_access(const char* what_arg) : logic_error{what_arg} - { - } - }; - - // workaround: std utility functions aren't constexpr yet - template - inline constexpr T&& mr_constexpr_forward(typename std::remove_reference::type& t) noexcept - { - return static_cast(t); - } - - template - inline constexpr T&& mr_constexpr_forward(typename std::remove_reference::type&& t) noexcept - { - static_assert(!std::is_lvalue_reference::value, "!!"); - return static_cast(t); - } - template inline constexpr typename std::remove_reference::type&& mr_constexpr_move(T&& t) noexcept { @@ -58,309 +30,150 @@ namespace maybe { template class result final { private: - void copy_from(const result& other) noexcept; - void set_from(result& other) noexcept; - void init_ok(T&& value) noexcept; - void init_ok(const T& value) noexcept; - void init_err(E&& value) noexcept; - void init_err(const E& value) noexcept; - void clear() noexcept; - - internal::Value tag; - union { - T ok_val; - E err_val; - }; + std::experimental::optional var_ok; + std::experimental::optional var_err; public: typedef T ok_type; - typedef T err_type; + typedef E err_type; - result() : tag(internal::Value::NONE) + result() { } - // Internal initialization. - - /** Ok initializer */ - result(T&& value, internal::Placeholder) : tag(internal::Value::OK) + result(T&& value, internal::placeholder) : var_ok(std::forward(value)) { - init_ok(std::forward(value)); } - /** Ok initializer */ - result(const T& value, internal::Placeholder) : tag(internal::Value::OK) + result(internal::placeholder, E&& value) : var_err(std::forward(value)) { - init_ok(value); - } - - /** Err initializer */ - result(internal::Placeholder, E&& value) : tag(internal::Value::ERR) - { - init_err(std::forward(value)); - } - - /** 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 - { - copy_from(other); - } - - /** copy assignment */ - result& operator=(const result& other) noexcept - { - clear(); - copy_from(other); - return *this; - } - - /** move constructor */ - result(result&& other) noexcept - { - set_from(other); - } - - /** move assignment */ - result& operator=(result&& other) noexcept - { - clear(); - set_from(other); - return *this; - } - - /** destructor */ - ~result() - { - clear(); - }; - - // Static construction helpers. - - /** helper constructor for default ok value */ - constexpr static result default_ok() noexcept - { - return mr_constexpr_move(result(T(), internal::Placeholder{})); } /** helper constructor for ok value */ constexpr static result ok(T&& value) noexcept { - return mr_constexpr_move(result(std::forward(value), internal::Placeholder{})); + return std::forward>( + result(std::forward(value), internal::placeholder{})); } /** helper constructor for ok value */ - constexpr static result ok(const T& value) noexcept + constexpr static result err(E&& value) noexcept { - return mr_constexpr_move(result(value, internal::Placeholder{})); + return std::forward>( + result(internal::placeholder{}, std::forward(value))); } - /** helper constructor for default err value */ - constexpr static result default_err() noexcept - { - return mr_constexpr_move(result(internal::Placeholder{}, E())); - } - - /** helper constructor for err value */ - constexpr static result err(E&& err) noexcept - { - return mr_constexpr_move(result(internal::Placeholder{}, std::forward(err))); - } - - /** helper constructor for err value */ - constexpr static result err(const E& err) noexcept - { - return mr_constexpr_move(result(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_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); - } - - E& err_value() - { - return is_err() ? err_val - : (throw bad_result_access("bad err result access"), err_val); - } - -#endif - -#if MAYBE_RESULT_HAS_THIS_RVALUE_REFS == 1 - - template - constexpr T ok_value_or(V&& v) const& - { - return is_ok() ? ok_val : static_cast(mr_constexpr_forward(v)); - } - - template - constexpr E err_value_or(V&& v) const& - { - return is_err() ? err_val : static_cast(mr_constexpr_forward(v)); - } - -#if MAYBE_RESULT_HAS_MOVE_ACCESSORS == 1 - - template - MAYBE_RESULT_MUTABLE_CONSTEXPR T ok_value_or(V&& v) && - { - return is_ok() ? mr_constexpr_move(const_cast&>(*this).ok_val) - : static_cast(mr_constexpr_forward(v)); - } - - template - MAYBE_RESULT_MUTABLE_CONSTEXPR E err_value_or(V&& v) && - { - return is_err() ? mr_constexpr_move(const_cast&>(*this).err_val) - : static_cast(mr_constexpr_forward(v)); - } - -#else - - template - T ok_value_or(V&& v) && - { - return is_ok() ? mr_constexpr_move(const_cast&>(*this).ok_val) - : static_cast(mr_constexpr_forward(v)); - } - - template - E err_value_or(V&& v) && - { - return is_err() ? mr_constexpr_move(const_cast&>(*this).err_val) - : static_cast(mr_constexpr_forward(v)); - } - -#endif - -#else - - template - constexpr T ok_value_or(V&& v) const - { - return is_ok() ? ok_val : static_cast(mr_constexpr_forward(v)); - } - - template - constexpr E err_value_or(V&& v) const - { - return is_err() ? err_val : static_cast(mr_constexpr_forward(v)); - } - -#endif - // Inspection. - constexpr bool is_ok() const noexcept + inline bool is_ok() const noexcept { - return tag == internal::Value::OK; + return !!var_ok; } - constexpr bool is_err() const noexcept + inline bool is_err() const noexcept { - return tag == internal::Value::ERR; + return !!var_err; } - explicit constexpr operator bool() const noexcept + explicit inline operator bool() const noexcept { return is_ok(); } - // Unsafe access. +#if OPTIONAL_HAS_MOVE_ACCESSORS == 1 - constexpr T* ok_dataptr() noexcept + constexpr T const& ok_value() const& { - assert(is_ok()); - return const_cast(std::addressof(ok_val)); + return var_ok.value(); } - constexpr E* err_dataptr() noexcept + OPTIONAL_MUTABLE_CONSTEXPR T& ok_value() & { - assert(is_err()); - return const_cast(std::addressof(err_val)); + return var_ok.value(); } + OPTIONAL_MUTABLE_CONSTEXPR T&& ok_value() && + { + return std::move(var_ok.value()); + } + +#else + + T const& ok_value() const + { + return var_ok.value(); + } + + T& ok_value() + { + return var_ok.value(); + } + +#endif + +#if OPTIONAL_HAS_THIS_RVALUE_REFS == 1 + + template + constexpr T ok_value_or(V&& v) const& + { + return var_ok.value_or(std::forward(v)); + } + + template + OPTIONAL_MUTABLE_CONSTEXPR T ok_value_or(V&& v) && + { + return var_err.value_or(std::forward(v)); + } + +#endif + +#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 + + 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 + constexpr E err_value_or(V&& v) const& + { + return var_err.value_or(std::forward(v)); + } + + template + OPTIONAL_MUTABLE_CONSTEXPR E err_value_or(V&& v) && + { + return var_err.value_or(std::forward(v)); + } + +#endif + // Functional helpers. /** @@ -374,7 +187,7 @@ namespace maybe { * @return maybe::result */ template - auto map(F f) noexcept -> maybe::result::type, E>; + inline auto map(F f) noexcept -> maybe::result::type, E>; /** * Maps a result to result by always returning provided U value on success, @@ -386,7 +199,7 @@ namespace maybe { * @return maybe::result */ template - auto map_value(U value) noexcept -> maybe::result; + inline auto map_value(U value) noexcept -> maybe::result; /** * Maps a result to result (where U is return value of F(E)) by applying a @@ -400,8 +213,7 @@ namespace maybe { * @return maybe::result */ template - auto map_err(F f) noexcept - -> maybe::result::type>; + inline auto map_err(F f) noexcept -> maybe::result::type>; /** * Maps a result to result by always returning provided U value on error, @@ -413,7 +225,7 @@ namespace maybe { * @return maybe::result */ template - auto map_err_value(U value) noexcept -> maybe::result; + inline auto map_err_value(U value) noexcept -> maybe::result; /** * Calls op if the result is ok, otherwise returns the err value of self. @@ -424,18 +236,18 @@ namespace maybe { * @return maybe::result */ template - auto and_then(F op) noexcept -> typename std::result_of::type; + inline auto and_then(F op) noexcept -> typename std::result_of::type; template - auto into_err() noexcept -> R; + inline auto into_err() noexcept -> R; }; template constexpr bool operator==(const result& x, const result& y) { return x.is_ok() && y.is_ok() - ? *x.ok_dataptr() == *y.ok_dataptr() - : (x.is_err() && y.is_err() ? *x.err_dataptr() == *y.err_dataptr() : false); + ? x.ok_value() == y.ok_value() + : (x.is_err() && y.is_err() ? x.err_value() == y.err_value() : false); } template diff --git a/src/maybe/result.inline.hpp b/src/maybe/result.inline.hpp index 4a2bd5e..0802ac8 100644 --- a/src/maybe/result.inline.hpp +++ b/src/maybe/result.inline.hpp @@ -12,94 +12,9 @@ #include "result.hpp" -template -void maybe::result::copy_from(const result& other) noexcept -{ - using ::maybe::internal::Value; - - switch (other.tag) { - case Value::OK: - init_ok(other.ok_val); - break; - case Value::ERR: - init_err(other.err_val); - break; - default: - tag = Value::NONE; - break; - } -} - -template -void maybe::result::set_from(result& other) noexcept -{ - using ::maybe::internal::Value; - - switch (other.tag) { - case Value::OK: - init_ok(std::move(other.ok_val)); - break; - case Value::ERR: - init_err(std::move(other.err_val)); - break; - default: - tag = Value::NONE; - return; - } - other.tag = Value::NONE; -} - -template -void maybe::result::init_ok(T&& value) noexcept -{ - tag = ::maybe::internal::Value::OK; - ::new (static_cast(ok_dataptr())) T(std::forward(value)); -} - -template -void maybe::result::init_ok(const T& value) noexcept -{ - tag = ::maybe::internal::Value::OK; - ::new (static_cast(ok_dataptr())) T(value); -} - -template -void maybe::result::init_err(E&& value) noexcept -{ - tag = ::maybe::internal::Value::ERR; - ::new (static_cast(err_dataptr())) E(std::forward(value)); -} - -template -void maybe::result::init_err(const E& value) noexcept -{ - tag = ::maybe::internal::Value::ERR; - ::new (static_cast(err_dataptr())) E(value); -} - -template -void maybe::result::clear() noexcept -{ - using ::maybe::internal::Value; - - switch (tag) { - case Value::OK: - (&this->ok_val)->T::~T(); - tag = Value::NONE; - break; - case Value::ERR: - (&this->err_val)->E::~E(); - tag = Value::NONE; - break; - default: - break; - } -} - template template -auto maybe::result::map(F f) noexcept - -> maybe::result::type, E> +inline auto maybe::result::map(F f) noexcept -> maybe::result::type, E> { typedef maybe::result::type, E> return_result_t; @@ -111,7 +26,7 @@ auto maybe::result::map(F f) noexcept template template -auto maybe::result::map_value(U value) noexcept -> maybe::result +inline auto maybe::result::map_value(U value) noexcept -> maybe::result { if (is_err()) { return maybe::result::err(std::forward(err_value())); @@ -122,7 +37,7 @@ auto maybe::result::map_value(U value) noexcept -> maybe::result template template -auto maybe::result::map_err(F f) noexcept +inline auto maybe::result::map_err(F f) noexcept -> maybe::result::type> { typedef maybe::result::type> return_result_t; @@ -135,7 +50,7 @@ auto maybe::result::map_err(F f) noexcept template template -auto maybe::result::map_err_value(U value) noexcept -> maybe::result +inline auto maybe::result::map_err_value(U value) noexcept -> maybe::result { if (is_ok()) { return maybe::result::ok(std::forward(ok_value())); @@ -146,7 +61,7 @@ auto maybe::result::map_err_value(U value) noexcept -> maybe::result template template -auto maybe::result::and_then(F f) noexcept -> typename std::result_of::type +inline auto maybe::result::and_then(F f) noexcept -> typename std::result_of::type { typedef typename std::result_of::type result_t; @@ -158,7 +73,7 @@ auto maybe::result::and_then(F f) noexcept -> typename std::result_of template -auto maybe::result::into_err() noexcept -> R +inline auto maybe::result::into_err() noexcept -> R { if (is_err()) { return R::err(std::forward(err_value())); diff --git a/tests/result_tests.cpp b/tests/result_tests.cpp index 34fbd39..f922f82 100644 --- a/tests/result_tests.cpp +++ b/tests/result_tests.cpp @@ -12,7 +12,11 @@ public: destructor_fun = std::move(other.destructor_fun); other.destructor_fun = nullptr; } - NoCopy& operator=(NoCopy&& other) = default; + NoCopy& operator=(NoCopy&& other) { + std::swap(flag, other.flag); + std::swap(destructor_fun, other.destructor_fun); + return *this; + } NoCopy(int flag, std::function destructor_fun) : flag(flag), destructor_fun(destructor_fun) @@ -45,11 +49,11 @@ TEST_CASE("result") REQUIRE(res); REQUIRE(12 == res.ok_value()); -// auto other_ok = result::ok(12); -// REQUIRE(res == other_ok); -// -// auto other_different_ok = result::ok(42); -// REQUIRE(res != other_different_ok); + auto other_ok = result::ok(12); + REQUIRE(res == other_ok); + + auto other_different_ok = result::ok(42); + REQUIRE(res != other_different_ok); } SECTION("created err result returns err value") @@ -60,18 +64,18 @@ TEST_CASE("result") REQUIRE(!res); REQUIRE(12 == res.err_value()); -// auto other_err = result::err(12); -// REQUIRE(res == other_err); -// -// auto other_different_err = result::err(42); -// REQUIRE(res != other_different_err); + auto other_err = result::err(12); + REQUIRE(res == other_err); + + auto other_different_err = result::err(42); + REQUIRE(res != other_different_err); } SECTION("created ok not equal created result") { auto ok = result::ok(12); auto err = result::err(12); -// REQUIRE(ok != err); + REQUIRE(ok != err); } SECTION("throws exception if invalid value accessed") @@ -192,7 +196,7 @@ TEST_CASE("result") REQUIRE(ss.str() == "[err]"); } -#if MAYBE_RESULT_HAS_MOVE_ACCESSORS == 1 +#if OPTIONAL_HAS_MOVE_ACCESSORS == 1 SECTION("ok move accessor works") {