mirror of
https://github.com/TartanLlama/expected.git
synced 2025-08-03 02:44:30 +02:00
Merge branch 'master' into trivial_copy_fix
This commit is contained in:
@@ -11,6 +11,9 @@ target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR})
|
||||
set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/extensions.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/assignment.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/emplace.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/bases.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/observers.cpp
|
||||
${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp)
|
||||
|
||||
add_executable(tests ${TEST_SOURCES})
|
||||
|
@@ -49,7 +49,7 @@ The interface is the same as `std::expected` as proposed in [p0323r3](http://www
|
||||
* `tl::expected<std::size_t,std::error_code> s = exp_string.map(&std::string::size);`
|
||||
- `map_error`: carries out some operation on the unexpected object if there is one.
|
||||
* `my_error_code translate_error (std::error_code);`
|
||||
* `tl::expected<int,my_error_code> s = exp_int.map(translate_error);`
|
||||
* `tl::expected<int,my_error_code> s = exp_int.map_error(translate_error);`
|
||||
- `and_then`: like `map`, but for operations which return a `tl::expected`.
|
||||
* `tl::expected<ast, fail_reason> parse (const std::string& s);`
|
||||
* `tl::expected<ast, fail_reason> exp_ast = exp_string.and_then(parse);`
|
||||
@@ -82,6 +82,10 @@ Requires [Standardese](https://github.com/foonathan/standardese) for generating
|
||||
|
||||
Requires [Catch](https://github.com/philsquared/Catch) for testing. This is bundled in the test directory.
|
||||
|
||||
### Acknowledgements
|
||||
|
||||
Thanks to [Kévin Alexandre Boissonneault](https://github.com/KABoissonneault) for various bug fixes.
|
||||
|
||||
----------
|
||||
|
||||
[]("http://creativecommons.org/publicdomain/zero/1.0/")
|
||||
|
101
expected.hpp
101
expected.hpp
@@ -158,8 +158,8 @@ constexpr bool operator>=(const unexpected<E> &lhs, const unexpected<E> &rhs) {
|
||||
/// *Example:*
|
||||
/// auto e1 = tl::make_unexpected(42);
|
||||
/// unexpected<int> e2 (42); //same semantics
|
||||
template <class E> unexpected<E> make_unexpected(E &&e) {
|
||||
return unexpected<E>(std::forward<E>(e));
|
||||
template <class E> unexpected<typename std::decay<E>::type> make_unexpected(E &&e) {
|
||||
return unexpected<typename std::decay<E>::type>(std::forward<E>(e));
|
||||
}
|
||||
|
||||
/// \brief A tag type to tell expected to construct the unexpected value
|
||||
@@ -631,7 +631,8 @@ struct expected_copy_base<T, E, false> : expected_operations_base<T, E> {
|
||||
// move constructible
|
||||
#ifndef TL_EXPECTED_GCC49
|
||||
template <class T, class E,
|
||||
bool = std::is_trivially_move_constructible<T>::value>
|
||||
bool = std::is_trivially_move_constructible<T>::value
|
||||
&& std::is_trivially_move_constructible<E>::value>
|
||||
struct expected_move_base : expected_copy_base<T, E> {
|
||||
using expected_copy_base<T, E>::expected_copy_base;
|
||||
};
|
||||
@@ -662,7 +663,10 @@ struct expected_move_base<T, E, false> : expected_copy_base<T, E> {
|
||||
template <class T, class E,
|
||||
bool = IS_TRIVIALLY_COPY_ASSIGNABLE(T) &&
|
||||
IS_TRIVIALLY_COPY_CONSTRUCTIBLE(T) &&
|
||||
IS_TRIVIALLY_DESTRUCTIBLE(T)>
|
||||
IS_TRIVIALLY_DESTRUCTIBLE(T) &&
|
||||
IS_TRIVIALLY_COPY_ASSIGNABLE(E) &&
|
||||
IS_TRIVIALLY_COPY_CONSTRUCTIBLE(E) &&
|
||||
IS_TRIVIALLY_DESTRUCTIBLE(E)>
|
||||
struct expected_copy_assign_base : expected_move_base<T, E> {
|
||||
using expected_move_base<T, E>::expected_move_base;
|
||||
};
|
||||
@@ -691,8 +695,11 @@ struct expected_copy_assign_base<T, E, false> : expected_move_base<T, E> {
|
||||
#ifndef TL_EXPECTED_GCC49
|
||||
template <class T, class E,
|
||||
bool = std::is_trivially_destructible<T>::value
|
||||
&&std::is_trivially_move_constructible<T>::value
|
||||
&&std::is_trivially_move_assignable<T>::value>
|
||||
&&std::is_trivially_move_constructible<T>::value
|
||||
&&std::is_trivially_move_assignable<T>::value
|
||||
&&std::is_trivially_destructible<E>::value
|
||||
&&std::is_trivially_move_constructible<E>::value
|
||||
&&std::is_trivially_move_assignable<E>::value>
|
||||
struct expected_move_assign_base : expected_copy_assign_base<T, E> {
|
||||
using expected_copy_assign_base<T, E>::expected_copy_assign_base;
|
||||
};
|
||||
@@ -709,14 +716,17 @@ struct expected_move_assign_base<T, E, false>
|
||||
expected_move_assign_base(const expected_move_assign_base &rhs) = default;
|
||||
|
||||
expected_move_assign_base(expected_move_assign_base &&rhs) = default;
|
||||
|
||||
expected_move_assign_base &
|
||||
operator=(const expected_move_assign_base &rhs) = default;
|
||||
|
||||
expected_move_assign_base &
|
||||
|
||||
operator=(expected_move_assign_base &&rhs) noexcept(
|
||||
std::is_nothrow_move_constructible<T>::value
|
||||
&&std::is_nothrow_move_assignable<T>::value) {
|
||||
this->assign(std::move(rhs));
|
||||
return *this;
|
||||
std::is_nothrow_move_constructible<T>::value
|
||||
&&std::is_nothrow_move_assignable<T>::value) {
|
||||
this->assign(std::move(rhs));
|
||||
return *this;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -929,9 +939,9 @@ public:
|
||||
/// \group and_then
|
||||
/// Carries out some operation which returns an expected on the stored object
|
||||
/// if there is one. \requires `std::invoke(std::forward<F>(f), value())`
|
||||
/// returns a `std::expected<U>` for some `U`. \returns Let `U` be the result
|
||||
/// of `std::invoke(std::forward<F>(f), value())`. Returns a
|
||||
/// `std::expected<U>`. The return value is empty if `*this` is empty,
|
||||
/// returns an `expected<U>` for some `U`. \returns Let `U` be the result
|
||||
/// of `std::invoke(std::forward<F>(f), value())`. Returns an
|
||||
/// `expected<U>`. The return value is empty if `*this` is empty,
|
||||
/// otherwise the return value of `std::invoke(std::forward<F>(f), value())`
|
||||
/// is returned.
|
||||
/// \synopsis template <class F>\nconstexpr auto and_then(F &&f) &;
|
||||
@@ -983,9 +993,9 @@ public:
|
||||
/// \group and_then
|
||||
/// Carries out some operation which returns an expected on the stored object
|
||||
/// if there is one. \requires `std::invoke(std::forward<F>(f), value())`
|
||||
/// returns a `std::expected<U>` for some `U`. \returns Let `U` be the result
|
||||
/// of `std::invoke(std::forward<F>(f), value())`. Returns a
|
||||
/// `std::expected<U>`. The return value is empty if `*this` is empty,
|
||||
/// returns an `expected<U>` for some `U`. \returns Let `U` be the result
|
||||
/// of `std::invoke(std::forward<F>(f), value())`. Returns an
|
||||
/// `expected<U>`. The return value is empty if `*this` is empty,
|
||||
/// otherwise the return value of `std::invoke(std::forward<F>(f), value())`
|
||||
/// is returned.
|
||||
/// \synopsis template <class F>\nconstexpr auto and_then(F &&f) &;
|
||||
@@ -1042,7 +1052,8 @@ public:
|
||||
!defined(TL_EXPECTED_GCC54)
|
||||
/// \brief Carries out some operation on the stored object if there is one.
|
||||
/// \returns Let `U` be the result of `std::invoke(std::forward<F>(f),
|
||||
/// value())`. Returns a `std::expected<U,E>`. If `*this` is unexpected, the
|
||||
/// value())`. If `U` is `void`, returns an `expected<monostate,E>, otherwise
|
||||
// returns an `expected<U,E>`. If `*this` is unexpected, the
|
||||
/// result is `*this`, otherwise an `expected<U,E>` is constructed from the
|
||||
/// return value of `std::invoke(std::forward<F>(f), value())` and is
|
||||
/// returned.
|
||||
@@ -1073,7 +1084,8 @@ public:
|
||||
#else
|
||||
/// \brief Carries out some operation on the stored object if there is one.
|
||||
/// \returns Let `U` be the result of `std::invoke(std::forward<F>(f),
|
||||
/// value())`. Returns a `std::expected<U,E>`. If `*this` is unexpected, the
|
||||
/// value())`. If `U` is `void`, returns an `expected<monostate,E>, otherwise
|
||||
// returns an `expected<U,E>`. If `*this` is unexpected, the
|
||||
/// result is `*this`, otherwise an `expected<U,E>` is constructed from the
|
||||
/// return value of `std::invoke(std::forward<F>(f), value())` and is
|
||||
/// returned.
|
||||
@@ -1122,7 +1134,8 @@ public:
|
||||
/// \brief Carries out some operation on the stored unexpected object if there
|
||||
/// is one.
|
||||
/// \returns Let `U` be the result of `std::invoke(std::forward<F>(f),
|
||||
/// value())`. Returns a `std::expected<T,U>`. If `*this` has an expected
|
||||
/// value())`. If `U` is `void`, returns an `expected<T,monostate>`, otherwise
|
||||
/// returns an `expected<T,U>`. If `*this` has an expected
|
||||
/// value, the result is `*this`, otherwise an `expected<T,U>` is constructed
|
||||
/// from `make_unexpected(std::invoke(std::forward<F>(f), value()))` and is
|
||||
/// returned.
|
||||
@@ -1154,7 +1167,7 @@ public:
|
||||
/// \brief Carries out some operation on the stored unexpected object if there
|
||||
/// is one.
|
||||
/// \returns Let `U` be the result of `std::invoke(std::forward<F>(f),
|
||||
/// value())`. Returns a `std::expected<T,U>`. If `*this` has an expected
|
||||
/// value())`. Returns an `expected<T,U>`. If `*this` has an expected
|
||||
/// value, the result is `*this`, otherwise an `expected<T,U>` is constructed
|
||||
/// from `make_unexpected(std::invoke(std::forward<F>(f), value()))` and is
|
||||
/// returned.
|
||||
@@ -1630,6 +1643,7 @@ public:
|
||||
|
||||
/// \exclude
|
||||
namespace detail {
|
||||
template <class Exp> using exp_t = typename detail::decay_t<Exp>::error_type;
|
||||
template <class Exp> using err_t = typename detail::decay_t<Exp>::error_type;
|
||||
template <class Exp, class Ret> using ret_t = expected<Ret, err_t<Exp>>;
|
||||
|
||||
@@ -1639,7 +1653,7 @@ template <class Exp, class F,
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
|
||||
constexpr auto map_impl(Exp &&exp, F &&f) {
|
||||
using result = ret_t<Exp, Ret>;
|
||||
using result = ret_t<Exp, detail::decay_t<Ret>>;
|
||||
return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
|
||||
*std::forward<Exp>(exp)))
|
||||
: result(unexpect, std::forward<Exp>(exp).error());
|
||||
@@ -1664,8 +1678,8 @@ template <class Exp, class F,
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
|
||||
|
||||
constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t<Exp, Ret> {
|
||||
using result = ret_t<Exp, Ret>;
|
||||
constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t<Exp, detail::decay_t<Ret>> {
|
||||
using result = ret_t<Exp, detail::decay_t<Ret>>;
|
||||
|
||||
return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
|
||||
*std::forward<Exp>(exp)))
|
||||
@@ -1691,26 +1705,57 @@ auto map_impl(Exp &&exp, F &&f) -> expected<monostate, err_t<Exp>> {
|
||||
!defined(TL_EXPECTED_GCC54)
|
||||
template <class Exp, class F,
|
||||
class Ret = decltype(detail::invoke(std::declval<F>(),
|
||||
*std::declval<Exp>()))>
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
|
||||
constexpr auto map_error_impl(Exp &&exp, F &&f) {
|
||||
using result = ret_t<Exp, Ret>;
|
||||
using result = expected<exp_t<Exp>, detail::decay_t<Ret>>;
|
||||
return exp.has_value()
|
||||
? result(*std::forward<Exp>(exp))
|
||||
: result(unexpect, detail::invoke(std::forward<F>(f),
|
||||
std::forward<Exp>(exp).error()));
|
||||
}
|
||||
template <class Exp, class F,
|
||||
class Ret = decltype(detail::invoke(std::declval<F>(),
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
|
||||
auto map_error_impl(Exp &&exp, F &&f) {
|
||||
using result = expected<exp_t<Exp>, monostate>;
|
||||
if (exp.has_value()) {
|
||||
return result(*std::forward<Exp>(exp));
|
||||
}
|
||||
|
||||
detail::invoke(std::forward<F>(f),
|
||||
std::forward<Exp>(exp).error());
|
||||
return result(unexpect, monostate{});
|
||||
}
|
||||
#else
|
||||
template <class Exp, class F,
|
||||
class Ret = decltype(detail::invoke(std::declval<F>(),
|
||||
*std::declval<Exp>()))>
|
||||
constexpr auto map_error_impl(Exp &&exp, F &&f) -> ret_t<Exp, Ret> {
|
||||
using result = ret_t<Exp, Ret>;
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
|
||||
constexpr auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, detail::decay_t<Ret>> {
|
||||
using result = ret_t<Exp, detail::decay_t<Ret>>;
|
||||
|
||||
return exp.has_value()
|
||||
? result(*std::forward<Exp>(exp))
|
||||
: result(unexpect, detail::invoke(std::forward<F>(f),
|
||||
std::forward<Exp>(exp).error()));
|
||||
}
|
||||
|
||||
template <class Exp, class F,
|
||||
class Ret = decltype(detail::invoke(std::declval<F>(),
|
||||
*std::declval<Exp>())),
|
||||
detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
|
||||
auto map_error_impl(Exp &&exp, F &&f) -> expected<exp_t<Exp>, monostate> {
|
||||
using result = expected<exp_t<Exp>, monostate>;
|
||||
if (exp.has_value()) {
|
||||
return result(*std::forward<Exp>(exp));
|
||||
}
|
||||
|
||||
detail::invoke(std::forward<F>(f),
|
||||
std::forward<Exp>(exp).error());
|
||||
return result(unexpect, monostate{});
|
||||
}
|
||||
#endif
|
||||
|
||||
template <class Exp, class F,
|
||||
|
@@ -130,6 +130,15 @@ TEST_CASE("Map extensions", "[extensions.map]") {
|
||||
STATIC_REQUIRE(
|
||||
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
|
||||
}
|
||||
|
||||
|
||||
// mapping functions which return references
|
||||
{
|
||||
tl::expected<int, int> e(42);
|
||||
auto ret = e.map([](int& i) -> int& { return i; });
|
||||
REQUIRE(ret);
|
||||
REQUIRE(ret == 42);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Map error extensions", "[extensions.map_error]") {
|
||||
@@ -191,6 +200,55 @@ TEST_CASE("Map error extensions", "[extensions.map_error]") {
|
||||
REQUIRE(!ret);
|
||||
REQUIRE(ret.error() == 42);
|
||||
}
|
||||
|
||||
{
|
||||
tl::expected<int, int> e = 21;
|
||||
auto ret = e.map_error(ret_void);
|
||||
REQUIRE(ret);
|
||||
}
|
||||
|
||||
{
|
||||
const tl::expected<int, int> e = 21;
|
||||
auto ret = e.map_error(ret_void);
|
||||
REQUIRE(ret);
|
||||
}
|
||||
|
||||
{
|
||||
tl::expected<int, int> e = 21;
|
||||
auto ret = std::move(e).map_error(ret_void);
|
||||
REQUIRE(ret);
|
||||
}
|
||||
|
||||
{
|
||||
const tl::expected<int, int> e = 21;
|
||||
auto ret = std::move(e).map_error(ret_void);
|
||||
REQUIRE(ret);
|
||||
}
|
||||
|
||||
{
|
||||
tl::expected<int, int> e(tl::unexpect, 21);
|
||||
auto ret = e.map_error(ret_void);
|
||||
REQUIRE(!ret);
|
||||
}
|
||||
|
||||
{
|
||||
const tl::expected<int, int> e(tl::unexpect, 21);
|
||||
auto ret = e.map_error(ret_void);
|
||||
REQUIRE(!ret);
|
||||
}
|
||||
|
||||
{
|
||||
tl::expected<int, int> e(tl::unexpect, 21);
|
||||
auto ret = std::move(e).map_error(ret_void);
|
||||
REQUIRE(!ret);
|
||||
}
|
||||
|
||||
{
|
||||
const tl::expected<int, int> e(tl::unexpect, 21);
|
||||
auto ret = std::move(e).map_error(ret_void);
|
||||
REQUIRE(!ret);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
TEST_CASE("And then extensions", "[extensions.and_then]") {
|
||||
|
Reference in New Issue
Block a user