Map and and_then with tests

This commit is contained in:
Simon Brand
2017-10-27 12:08:25 +01:00
parent 8b05109ead
commit dddf72ee34
3 changed files with 335 additions and 17 deletions

View File

@@ -9,6 +9,7 @@ target_include_directories(Catch INTERFACE ${CATCH_INCLUDE_DIR})
# Make test executable
set(TEST_SOURCES ${CMAKE_CURRENT_SOURCE_DIR}/tests/main.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/extensions.cpp
${CMAKE_CURRENT_SOURCE_DIR}/tests/constructors.cpp)
add_executable(tests ${TEST_SOURCES})

View File

@@ -50,11 +50,19 @@
#endif
namespace tl {
template <class T, class E> class expected;
namespace detail {
template <bool E, class T = void>
using enable_if_t = typename std::enable_if<E, T>::type;
template <class T> using decay_t = typename std::decay<T>::type;
// Trait for checking if a type is a tl::expected
template <class T> struct is_expected_impl : std::false_type {};
template <class T, class E>
struct is_expected_impl<expected<T, E>> : std::true_type {};
template <class T> using is_expected = is_expected_impl<decay_t<T>>;
// std::invoke from C++17
// https://stackoverflow.com/questions/38288042/c11-14-invoke-workaround
template <typename Fn, typename... Args,
@@ -424,6 +432,60 @@ public:
typedef E error_type;
typedef unexpected<E> unexpected_type;
/// \group and_then
/// Carries out some operation which returns an optional on the stored object
/// if there is one. \requires `std::invoke(std::forward<F>(f), value())`
/// returns a `std::optional<U>` for some `U`. \returns Let `U` be the result
/// of `std::invoke(std::forward<F>(f), value())`. Returns a
/// `std::optional<U>`. The return value is empty if `*this` is empty,
/// otherwise the return value of `std::invoke(std::forward<F>(f), value())`
/// is returned. \group and_then \synopsis template <class F>\nconstexpr auto
/// and_then(F &&f) &;
template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) & {
using result = detail::invoke_result_t<F, T &>;
static_assert(detail::is_expected<result>::value,
"F must return an expected");
return has_value() ? detail::invoke(std::forward<F>(f), **this)
: result(unexpect, this->error());
}
/// \group and_then
/// \synopsis template <class F>\nconstexpr auto and_then(F &&f) &&;
template <class F> TL_EXPECTED_11_CONSTEXPR auto and_then(F &&f) && {
using result = detail::invoke_result_t<F, T &&>;
static_assert(detail::is_expected<result>::value,
"F must return an expected");
return has_value() ? detail::invoke(std::forward<F>(f), std::move(**this))
: result(unexpect, std::move(this->error()));
}
/// \group and_then
/// \synopsis template <class F>\nconstexpr auto and_then(F &&f) const &;
template <class F> constexpr auto and_then(F &&f) const & {
using result = detail::invoke_result_t<F, const T &>;
static_assert(detail::is_expected<result>::value,
"F must return an expected");
return has_value() ? detail::invoke(std::forward<F>(f), **this)
: result(unexpect, this->error());
}
#ifndef TL_EXPECTED_NO_CONSTRR
/// \group and_then
/// \synopsis template <class F>\nconstexpr auto and_then(F &&f) const &&;
template <class F> constexpr auto and_then(F &&f) const && {
using result = detail::invoke_result_t<F, const T &&>;
static_assert(detail::is_expected<result>::value,
"F must return an expected");
return has_value() ? detail::invoke(std::forward<F>(f), std::move(**this))
: result(unexpect, std::
: move(this->error()));
}
#endif
#ifdef TL_EXPECTED_CXX14
/// \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),
@@ -691,28 +753,33 @@ public:
}
private:
#ifdef TL_EXPECTED_CX14
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>>;
#ifdef TL_EXPECTED_CXX14
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>
constexpr auto map_impl(Exp &&exp, F &&f) {
return exp.has_value()
? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
: unexpected<typename Exp::error_type>(std::forward<Exp>(exp).error());
static constexpr auto map_impl(Exp &&exp, F &&f) {
using result = ret_t<Exp, Ret>;
return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
*std::forward<Exp>(exp)))
: result(unexpect, 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_impl(Exp &&exp, F &&f) {
static auto map_impl(Exp &&exp, F &&f) {
using result = expected<monostate, err_t<Exp>>;
if (exp.has_value()) {
detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
return monostate{};
return result(monostate{});
}
return unexpected<typename Exp::error_type>(std::forward<Exp>(exp).error());
return result(unexpect, std::forward<Exp>(exp).error());
}
#else
template <class Exp, class F,
@@ -720,11 +787,12 @@ private:
*std::declval<Exp>())),
detail::enable_if_t<!std::is_void<Ret>::value> * = nullptr>
constexpr auto map_impl(Exp &&exp, F &&f)
-> expected<Ret, typename Exp::error_type> {
return exp.has_value()
? detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp))
: unexpected<typename Exp::error_type>(std::forward<Exp>(exp).error());
static constexpr auto map_impl(Exp &&exp, F &&f) -> ret_t<Exp, Ret> {
using result = ret_t<Exp, Ret>;
return exp.has_value() ? result(detail::invoke(std::forward<F>(f),
*std::forward<Exp>(exp)))
: result(unexpect, std::forward<Exp>(exp).error());
}
template <class Exp, class F,
@@ -732,14 +800,13 @@ private:
*std::declval<Exp>())),
detail::enable_if_t<std::is_void<Ret>::value> * = nullptr>
auto map_impl(Exp &&exp, F &&f)
-> expected<monostate, typename Exp::error_type> {
static auto map_impl(Exp &&exp, F &&f) -> expected<monostate, err_t<Exp>> {
if (exp.has_value()) {
detail::invoke(std::forward<F>(f), *std::forward<Exp>(exp));
return std::forward<Exp>(exp).error();
return tl::monostate{};
}
return unexpected<typename Exp::error_type>(std::forward<Exp>(exp).error());
return unexpected<err_t<Exp>>(std::forward<Exp>(exp).error());
}
#endif
};

250
tests/extensions.cpp Normal file
View File

@@ -0,0 +1,250 @@
#include "catch.hpp"
#include "expected.hpp"
#define TOKENPASTE(x, y) x##y
#define TOKENPASTE2(x, y) TOKENPASTE(x, y)
#define STATIC_REQUIRE(e) \
constexpr bool TOKENPASTE2(rqure, __LINE__) = e; \
REQUIRE(e);
TEST_CASE("Map extensions", "[extensions.map]") {
auto mul2 = [](auto a) { return a * 2; };
auto ret_void = [](auto a) {};
{
tl::expected<int, int> e = 21;
auto ret = e.map(mul2);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
const tl::expected<int, int> e = 21;
auto ret = e.map(mul2);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
tl::expected<int, int> e = 21;
auto ret = std::move(e).map(mul2);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).map(mul2);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.map(mul2);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.map(mul2);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).map(mul2);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).map(mul2);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
tl::expected<int, int> e = 21;
auto ret = e.map(ret_void);
REQUIRE(ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
const tl::expected<int, int> e = 21;
auto ret = e.map(ret_void);
REQUIRE(ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
tl::expected<int, int> e = 21;
auto ret = std::move(e).map(ret_void);
REQUIRE(ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).map(ret_void);
REQUIRE(ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.map(ret_void);
REQUIRE(!ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.map(ret_void);
REQUIRE(!ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).map(ret_void);
REQUIRE(!ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).map(ret_void);
REQUIRE(!ret);
STATIC_REQUIRE(
(std::is_same<decltype(ret), tl::expected<tl::monostate, int>>::value));
}
}
TEST_CASE("And then extensions", "[extensions.and_then]") {
auto succeed = [](auto a) { return tl::expected<int, int>(21 * 2); };
auto fail = [](auto a) { return tl::expected<int, int>(tl::unexpect, 17); };
{
tl::expected<int, int> e = 21;
auto ret = e.and_then(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
const tl::expected<int, int> e = 21;
auto ret = e.and_then(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
tl::expected<int, int> e = 21;
auto ret = std::move(e).and_then(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).and_then(succeed);
REQUIRE(ret);
REQUIRE(*ret == 42);
}
{
tl::expected<int, int> e = 21;
auto ret = e.and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}
{
const tl::expected<int, int> e = 21;
auto ret = e.and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}
{
tl::expected<int, int> e = 21;
auto ret = std::move(e).and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}
{
const tl::expected<int, int> e = 21;
auto ret = std::move(e).and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 17);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_then(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_then(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_then(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_then(succeed);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = e.and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
{
const tl::expected<int, int> e(tl::unexpect, 21);
auto ret = std::move(e).and_then(fail);
REQUIRE(!ret);
REQUIRE(ret.error() == 21);
}
}