Topic/expected monadic operations (#1219)

* topic/expected-monadic-operations:
	- added and_then, or_else, transform, and transform_error with simple tests

* topic/expected-monadic-operation:
	- added void TValue specialization operations
	- updated unit tests to include expected<void, TError>
	- added is_expected to expected.h, used in and_then to ensure that the returned type is an expected

* topic/expected-monadic-operations:
	- made implementation c++11 compatible

* topic/expected-monadic-operations:
	- started addressing coderabbit feedback

* topic/expected-monadic-operations:
	- adapted invoke to etl
	- reworked return type deduction
	- filled in or_else unit tests to be more complete
	- still need to add invoke unit tests

* topic/expected-monadic-operations:
	-c++14 compliance

* topic/expected-monadic-operations:
	- completed coderabbit suggestions

* topic/expected-monadic-operations:
	- formatting in invoke and expected
	- added test suite for invoke

* topic/expected-monadic-operations:
	- fixed missing moves for const TValue&& and const TError&&

* topic/expected-monadic-operations:
	- made everything c++11 compatible, very verbose as a result

* topic/expected-monadic-operations:
	- fixed code rabbit rewivew for move semantics in const && overloads

* topic/expected-monadic-operations:
	- moved around a move

* topic/expected-monadic-operations:
	- added etl:: to invoke_result calls that were missing it

* topic/expected-monadic-operations:
	- formatting

* topic/expected-monadic-operations:
	- added invoke for void f() calls for consistency

* topic/expected-monadic-operations:
	- reworked entire thing to be able to handle expected<T,E> to expected<void,E> without even more code duplication

* topic/expected-monadic-operations:
	- added trailing return type to maintain c++11 compatibility

* topic/expected-monadic-operations:
	- fixed mismatch between deduced type and return for a few functions

* topic/expected-monadic-operations:
	- replaced calls to get<TError> and get<TValue> with get<Error_Type> and get<Value_Type>

---------

Co-authored-by: Jon Whitfield <jon@volumetrix.com>
This commit is contained in:
whitfijs-jw 2025-12-13 03:04:38 -05:00 committed by John Wellbelove
parent 520b4a9f46
commit 269f3ffdfd
5 changed files with 1285 additions and 1 deletions

View File

@ -39,9 +39,20 @@ SOFTWARE.
#include "utility.h"
#include "variant.h"
#include "initializer_list.h"
#include "type_traits.h"
#include "invoke.h"
namespace etl
{
// Forward declaration for is_expected
template <typename TValue, typename TError> class expected;
template <typename T>
struct is_expected : etl::false_type {};
template <typename TValue, typename TError>
struct is_expected<expected<TValue,TError>> : etl::true_type {};
//***************************************************************************
/// Base exception for et::expected
//***************************************************************************
@ -732,6 +743,104 @@ namespace etl
}
#endif
#if ETL_USING_CPP11
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TValue&>::type>::type>
auto transform(F&& f) & -> expected<U, TError>
{
return transform_impl<F, this_type&, U, TValue&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TValue&>::type>::type>
auto transform(F&& f) const& -> expected<U, TError>
{
return transform_impl<F, const this_type&, U, const TValue&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TValue&&>::type>::type>
auto transform(F&& f) && -> expected<U, TError>
{
return transform_impl<F, this_type&&, U, TValue&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TValue&&>::type>::type>
auto transform(F&& f) const&& -> expected<U, TError>
{
return transform_impl<F, const this_type&&, U, const TValue&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TValue&>::type>::type>
auto and_then(F&& f) & -> U
{
return and_then_impl<F, this_type&, U, TValue&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TValue&>::type>::type>
auto and_then(F&& f) const& -> U
{
return and_then_impl<F, const this_type&, U, const TValue&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TValue&&>::type>::type>
auto and_then(F&& f) && -> U
{
return and_then_impl<F, this_type&&, U, TValue&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TValue&&>::type>::type>
auto and_then(F&& f) const&& -> U
{
return and_then_impl<F, const this_type&&, U, const TValue&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&>::type>::type>
auto or_else(F&& f) & -> U
{
return or_else_impl<F, this_type&, U, TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&>::type>::type>
auto or_else(F&& f) const & -> U
{
return or_else_impl<F, const this_type&, U, const TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&&>::type>::type>
auto or_else(F&& f) && -> U
{
return or_else_impl<F, this_type&&, U, TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&&>::type>::type>
auto or_else(F&& f) const && -> U
{
return or_else_impl<F, const this_type&&, U, const TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&>::type>::type>
auto transform_error(F&& f) & -> expected<TValue, U>
{
return transform_error_impl<F, this_type&, U, TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&>::type>::type>
auto transform_error(F&& f) const & -> expected<TValue, U>
{
return transform_error_impl<F, const this_type&, U, const TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&&>::type>::type>
auto transform_error(F&& f) && -> expected<TValue, U>
{
return transform_error_impl<F, this_type&&, U, TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&&>::type>::type>
auto transform_error(F&& f) const&& -> expected<TValue, U>
{
return transform_error_impl<F, const this_type&&, U, const TError&&>(etl::forward<F>(f), etl::move(*this));
}
#endif
private:
enum
@ -743,6 +852,72 @@ namespace etl
typedef etl::variant<etl::monostate, value_type, error_type> storage_type;
storage_type storage;
template <typename F, typename TExp, typename TRet, typename TValueRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value>::type>
auto transform_impl(F&& f, TExp&& exp) const -> expected<TRet, TError>
{
if (exp.has_value())
{
return expected<TRet, TError>(etl::invoke(etl::forward<F>(f), etl::forward<TValueRef>(etl::get<Value_Type>(exp.storage))));
}
else
{
return expected<TRet, TError>(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename TValueRef, typename = typename etl::enable_if<etl::is_void<TRet>::value>::type>
auto transform_impl(F&& f, TExp&& exp) const -> expected<void, TError>
{
if (exp.has_value())
{
etl::invoke(etl::forward<F>(f), etl::forward<TValueRef>(etl::get<Value_Type>(exp.storage)));
return expected<void, TError>();
}
else
{
return expected<void, TError>(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename TValueRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value && etl::is_expected<TRet>::value && etl::is_same<typename TRet::error_type, TError>::value>::type>
auto and_then_impl(F&& f, TExp&& exp) const -> TRet
{
if (exp.has_value())
{
return etl::invoke(etl::forward<F>(f), etl::forward<TValueRef>(etl::get<Value_Type>(exp.storage)));
}
else
{
return TRet(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename TErrorRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value && etl::is_expected<TRet>::value && etl::is_same<typename TRet::value_type, TValue>::value>::type>
auto or_else_impl(F&& f, TExp&& exp) const -> TRet
{
if (exp.has_value())
{
return TRet(etl::forward<TExp>(exp).value());
}
else
{
return etl::invoke(etl::forward<F>(f), etl::forward<TErrorRef>(etl::get<Error_Type>(exp.storage)));
}
}
template <typename F, typename TExp, typename TRet, typename TErrorRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value>::type>
auto transform_error_impl(F&& f, TExp&& exp) const -> expected<TValue, TRet>
{
if (exp.has_value())
{
return expected<TValue, TRet>(etl::forward<TExp>(exp).value());
}
else
{
return expected<TValue, TRet>(unexpected<TRet>(etl::invoke(etl::forward<F>(f), etl::forward<TErrorRef>(etl::get<Error_Type>(exp.storage)))));
}
}
};
//*****************************************************************************
@ -942,6 +1117,105 @@ namespace etl
swap(storage, other.storage);
}
#if ETL_USING_CPP11
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto transform(F&& f) & -> expected<U, TError>
{
return transform_impl<F, this_type&, U>(etl::forward<F>(f), *this);
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto transform(F&& f) const & -> expected<U, TError>
{
return transform_impl<F, const this_type&, U>(etl::forward<F>(f), *this);
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto transform(F&& f) && -> expected<U, TError>
{
return transform_impl<F, this_type&&, U>(etl::forward<F>(f), etl::move(*this));
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto transform(F&& f) const && -> expected<U, TError>
{
return transform_impl<F, const this_type&&, U>(etl::forward<F>(f), etl::move(*this));
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto and_then(F&& f) & -> U
{
return and_then_impl<F, this_type&, U>(etl::forward<F>(f), *this);
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto and_then(F&& f) const & -> U
{
return and_then_impl<F, const this_type&, U>(etl::forward<F>(f), *this);
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto and_then(F&& f) && -> U
{
return and_then_impl<F, this_type&&, U>(etl::forward<F>(f), etl::move(*this));
}
template<typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void>::type>::type>
auto and_then(F&& f) const && -> U
{
return and_then_impl<F, const this_type&&, U>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&>::type>::type>
auto or_else(F&& f) & -> U
{
return or_else_impl<F, this_type&, U, TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&>::type>::type>
auto or_else(F&& f) const & -> U
{
return or_else_impl<F, const this_type&, U, const TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&&>::type>::type>
auto or_else(F&& f) && -> U
{
return or_else_impl<F, this_type&&, U, TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&&>::type>::type>
auto or_else(F&& f) const && -> U
{
return or_else_impl<F, const this_type&&, U, const TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&>::type>::type>
auto transform_error(F&& f) & -> expected<void, U>
{
return transform_error_impl<F, this_type&, U, TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&>::type>::type>
auto transform_error(F&& f) const & -> expected<void, U>
{
return transform_error_impl<F, const this_type&, U, const TError&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, TError&&>::type>::type>
auto transform_error(F&& f) && -> expected<void, U>
{
return transform_error_impl<F, this_type&&, U, TError&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref<typename etl::invoke_result<F, void, const TError&&>::type>::type>
auto transform_error(F&& f) const && -> expected<void, U>
{
return transform_error_impl<F, const this_type&&, U, const TError&&>(etl::forward<F>(f), *this);
}
#endif
private:
enum
@ -951,6 +1225,72 @@ namespace etl
};
etl::variant<etl::monostate, error_type> storage;
template <typename F, typename TExp, typename TRet, typename = typename etl::enable_if<!etl::is_void<TRet>::value>::type>
auto transform_impl(F&& f, TExp&& exp) const -> expected<TRet, TError>
{
if (exp.has_value())
{
return expected<TRet, TError>(etl::invoke(etl::forward<F>(f)));
}
else
{
return expected<TRet, TError>(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename = typename etl::enable_if<etl::is_void<TRet>::value>::type>
auto transform_impl(F&& f, TExp&& exp) const -> expected<void, TError>
{
if (exp.has_value())
{
etl::invoke(etl::forward<F>(f));
return expected<void, TError>();
}
else
{
return expected<void, TError>(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename = typename etl::enable_if<!etl::is_void<TRet>::value && etl::is_expected<TRet>::value && etl::is_same<typename TRet::error_type, TError>::value>::type>
auto and_then_impl(F&& f, TExp&& exp) const -> TRet
{
if (exp.has_value())
{
return etl::invoke(etl::forward<F>(f));
}
else
{
return TRet(unexpected<TError>(etl::forward<TExp>(exp).error()));
}
}
template <typename F, typename TExp, typename TRet, typename TErrorRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value && etl::is_expected<TRet>::value && etl::is_same<typename TRet::value_type, void>::value>::type>
auto or_else_impl(F&& f, TExp&& exp) const -> TRet
{
if (exp.has_value())
{
return TRet();
}
else
{
return etl::invoke(etl::forward<F>(f), etl::forward<TErrorRef>(etl::get<Error_Type>(exp.storage)));
}
}
template <typename F, typename TExp, typename TRet, typename TErrorRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value>::type>
auto transform_error_impl(F&& f, TExp&& exp) const -> expected<void, TRet>
{
if (exp.has_value())
{
return expected<void, TRet>();
}
else
{
return expected<void, TRet>(unexpected<TRet>(etl::invoke(etl::forward<F>(f), etl::forward<TErrorRef>(etl::get<Error_Type>(exp.storage)))));
}
}
};
}

148
include/etl/invoke.h Normal file
View File

@ -0,0 +1,148 @@
///\file
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2025 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#ifndef ETL_INVOKE_INCLUDED
#define ETL_INVOKE_INCLUDED
#include "platform.h"
#include "functional.h"
#include "function_traits.h"
#include "type_traits.h"
#include "utility.h"
namespace etl {
template <typename T> struct logical_not_t : etl::integral_constant<bool, !bool(T::value)> {};
/// is T a function -- a function cannot be const qualified like a variable
template<typename T> struct is_function : public etl::integral_constant<bool, !etl::is_const<const T>::value> { };
template<typename T> struct is_function<T&> : public etl::false_type { };
template<typename T> struct is_function<T&&> : public etl::false_type { };
/// is T a member pointer
template<typename T> struct is_member_pointer_helper : etl::false_type {};
template<typename T, typename C> struct is_member_pointer_helper<T C::*> : etl::true_type {};
template<typename T> struct is_member_pointer : is_member_pointer_helper<etl::remove_cv_t<T>> {};
/// is T a member function pointer
template <typename> struct is_member_function_pointer_helper : etl::false_type {};
template <typename T, typename C> struct is_member_function_pointer_helper<T C::*> : public etl::is_function<T>::type {};
template <typename T> struct is_member_function_pointer : public is_member_function_pointer_helper<etl::remove_cv_t<T>>::type {};
/// is T a member object pointer
template<typename> struct is_member_object_pointer_helper : public etl::false_type { };
template<typename T, typename C> struct is_member_object_pointer_helper<T C::*> : public logical_not_t<etl::is_function<T>>::type { };
template<typename T> struct is_member_object_pointer : public is_member_object_pointer_helper<etl::remove_cv_t<T>>::type {};
template <
typename F,
typename ... TArgs,
typename = typename etl::enable_if<
!etl::is_member_pointer<etl::decay_t<F>>::value>::type
>
ETL_CONSTEXPR auto invoke(F&& f, TArgs&& ... args)
-> decltype(etl::forward<F>(f)(etl::forward<TArgs>(args)...)) {
return etl::forward<F>(f)(etl::forward<TArgs>(args)...);
}
template <
typename F,
typename T,
typename... TArgs,
typename = typename etl::enable_if<
etl::is_member_function_pointer<etl::decay_t<F>>::value &&
!etl::is_pointer<etl::decay_t<T>>::value
>::type
>
ETL_CONSTEXPR auto invoke(F&& f, T&& t, TArgs&&... args)
-> decltype((etl::forward<T>(t).*f)(etl::forward<TArgs>(args)...))
{
return (etl::forward<T>(t).*f)(etl::forward<TArgs>(args)...);
}
template <
typename F,
typename T,
typename ... TArgs,
typename = typename etl::enable_if<
etl::is_member_function_pointer<etl::decay_t<F>>::value &&
etl::is_pointer<etl::decay_t<T>>::value
>::type
>
ETL_CONSTEXPR auto invoke(F&& f, T&& t, TArgs&&... args)
-> decltype(((*etl::forward<T>(t)).*f)(etl::forward<TArgs>(args)...))
{
return ((*etl::forward<T>(t)).*f)(etl::forward<TArgs>(args)...);
}
template <
typename F,
typename T,
typename = typename etl::enable_if<
etl::is_member_object_pointer<etl::decay_t<F>>::value &&
!etl::is_pointer<etl::decay_t<T>>::value
>::type
>
ETL_CONSTEXPR auto invoke(F&& f, T&& t)
-> decltype(etl::forward<T>(t).*f)
{
return etl::forward<T>(t).*f;
}
template <
typename F,
typename T,
typename = typename etl::enable_if<
etl::is_member_object_pointer<etl::decay_t<F>>::value &&
etl::is_pointer<etl::decay_t<T>>::value
>::type
>
ETL_CONSTEXPR auto invoke(F&& f, T&& t)
-> decltype(((*etl::forward<T>(t)).*f))
{
return ((*etl::forward<T>(t)).*f);
}
template <class F, class, class ... Us> struct invoke_result;
template <typename F, typename... Us>
struct invoke_result<
F,
etl::void_t<decltype(etl::invoke(etl::declval<F>(), etl::declval<Us>()...))>,
Us...
> {
using type = decltype(etl::invoke(etl::declval<F>(), etl::declval<Us>()...));
};
template <typename F, typename... Us>
using invoke_result_t = typename invoke_result<F, void, Us...>::type;
}
#endif

View File

@ -209,6 +209,7 @@ add_executable(etl_tests
test_invert.cpp
test_io_port.cpp
test_iterator.cpp
test_invoke.cpp
test_jenkins.cpp
test_largest.cpp
test_limiter.cpp

View File

@ -792,5 +792,616 @@ namespace
CHECK_TRUE(test_unexp_1_swap == test_unexp_2);
CHECK_TRUE(test_unexp_2_swap == test_unexp_1);
}
}
//*************************************************************************
template <typename TValue, typename TExpected, typename Enable = void>
struct value_type_helper
{
static bool check(TExpected& expected)
{
return etl::is_same<typename etl::decay<decltype(expected.value())>::type, TValue>::value;
}
};
template <typename TValue, typename TExpected>
struct value_type_helper<TValue, TExpected, typename etl::enable_if<etl::is_void<TValue>::value>::type>
{
static bool check(TExpected& expected)
{
(void)expected;
return true;
}
};
template <typename TValue, typename TError, typename TExpected>
bool check_expected_type_helper(TExpected& expected)
{
bool value_type_ok = value_type_helper<TValue, TExpected>::check(expected);
bool error_type_ok = etl::is_same<typename etl::decay<decltype(expected.error())>::type, TError>::value;
bool expected_type_ok = etl::is_same<typename etl::decay<decltype(expected)>::type, etl::expected<TValue, TError> >::value;
return value_type_ok && error_type_ok && expected_type_ok;
}
//*************************************************************************
TEST(test_or_else)
{
Expected expected = {Value("or_else_with_value")};
Expected expected_error = {Unexpected(Error("or_else_with_error"))};
const Expected expected_const = {Value("const_or_else_with_value")};
const Expected expected_error_const = {Unexpected(Error("const_or_else_with_error"))};
bool error_generated {false};
auto expected_out = expected.or_else([&error_generated](Error e)
-> Expected
{
error_generated = true;
return Unexpected(e);
});
CHECK_FALSE(error_generated);
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("or_else_with_value", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<Value, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
error_generated = false;
auto expected_const_out = expected_const.or_else([&error_generated](const Error& e) -> Expected {
error_generated = true;
return Unexpected(e);
});
CHECK_FALSE(error_generated);
CHECK_TRUE(expected_const_out.has_value());
CHECK_EQUAL("const_or_else_with_value", expected_const_out.value().v);
error_generated = false;
auto unexpected_out = expected_error.or_else([&error_generated](Error e) -> Expected {
error_generated = true;
return Unexpected(e);
});
CHECK_TRUE(error_generated);
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<Value,Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("or_else_with_error", unexpected_out.error().e);
error_generated = false;
auto unexpected_const_out = expected_error_const.or_else([&error_generated](const Error& e) -> Expected {
error_generated = true;
return Unexpected(e);
});
CHECK_TRUE(error_generated);
CHECK_FALSE(unexpected_const_out.has_value());
auto with_error_const_type_check = check_expected_type_helper<Value,Error>(unexpected_const_out);
CHECK_TRUE(with_error_const_type_check);
CHECK_EQUAL("const_or_else_with_error", unexpected_const_out.error().e);
}
//*************************************************************************
TEST(test_or_else_move_constructor) {
ExpectedM expected = ExpectedM(ValueM("or_else_with_value"));
ExpectedM expected_error = ExpectedM(UnexpectedM(ErrorM("or_else_with_error")));
bool error_generated {false};
auto expected_out = etl::move(expected).or_else([&error_generated](ErrorM e) -> ExpectedM {
error_generated = true;
UnexpectedM unexpected(etl::move(e));
return ExpectedM(etl::move(unexpected));
});
CHECK_FALSE(error_generated);
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("or_else_with_value", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<ValueM, ErrorM>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = etl::move(expected_error).or_else([&error_generated](ErrorM e) -> ExpectedM {
error_generated = true;
CHECK_EQUAL("or_else_with_error", e.e);
UnexpectedM unexpected(etl::move(e));
return ExpectedM(etl::move(unexpected));
});
CHECK_TRUE(error_generated);
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<ValueM,ErrorM>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("or_else_with_error", unexpected_out.error().e);
//The following should NOT compile. The const & overload should attempt to copy
// const ExpectedM expected_error_const = ExpectedM(ValueM("or_else_with_value"));
// expected_error_const.or_else([&error_generated](ErrorM e) -> const ExpectedM {
// error_generated = true;
// UnexpectedM unexpected(etl::move(e));
// return ExpectedM(etl::move(unexpected));
// });
}
//*************************************************************************
TEST(test_or_else_void) {
ExpectedV expected = ExpectedV();
ExpectedV expected_error = ExpectedV(Unexpected(Error("or_else_with_error")));
bool error_generated {false};
auto expected_out = expected.or_else([&error_generated](Error e) -> ExpectedV {
error_generated = true;
return Unexpected(e);
});
CHECK_FALSE(error_generated);
CHECK_TRUE(expected_out.has_value());
auto with_value_type_check = check_expected_type_helper<void, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = expected_error.or_else([&error_generated](Error e) -> ExpectedV {
error_generated = true;
CHECK_EQUAL("or_else_with_error", e.e);
return Unexpected(e);
});
CHECK_TRUE(error_generated);
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<void, Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("or_else_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_or_else_change_error) {
Expected expected_error = {Unexpected(Error("or_else_with_error"))};
ExpectedV expectedV_error = ExpectedV(Unexpected(Error("or_else_with_error")));
auto change_to_string = expectedV_error.or_else([](Error e) -> etl::expected<void, std::string> {
return etl::unexpected<std::string>(e.e.append("_to_string"));
});
auto with_error_type_check = check_expected_type_helper<void,std::string>(change_to_string);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("or_else_with_error_to_string", change_to_string.error());
}
//*************************************************************************
TEST(test_or_else_change_error_move_constructor) {
ExpectedM expected_error = ExpectedM(UnexpectedM(ErrorM("or_else_with_error")));
auto change_to_string = etl::move(expected_error).or_else([](ErrorM e) -> etl::expected<ValueM, std::string> {
return etl::unexpected<std::string>(e.e.append("_to_string"));
});
auto with_error_type_check = check_expected_type_helper<ValueM,std::string>(change_to_string);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("or_else_with_error_to_string", change_to_string.error());
}
//*************************************************************************
TEST(test_or_else_const_rvalue) {
bool error_generated {false};
auto temp_expected = Expected(Unexpected(Error("temp_const_error")));
auto unexpected_out = static_cast<const Expected&&>(temp_expected)
.or_else([&error_generated](const Error& e) -> Expected {
error_generated = true;
CHECK_EQUAL("temp_const_error", e.e);
return Expected(Unexpected(etl::move(e)));
});
CHECK_TRUE(error_generated);
CHECK_EQUAL("temp_const_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_transform) {
Expected expected = {Value("transform_with_value")};
Expected expected_error = {Unexpected(Error("transform_with_error"))};
const Expected expected_const = {Value("const_transform_with_value")};
auto expected_out = expected.transform([](Value v) {
auto s = v.v.append("_transformed");
return s;
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("transform_with_value_transformed", expected_out.value());
auto with_value_type_check = check_expected_type_helper<std::string, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
auto expected_out_const = expected_const.transform([](const Value& v) {
auto s = v;
return s.v.append("_transformed");
});
CHECK_TRUE(expected_out_const.has_value());
CHECK_EQUAL("const_transform_with_value_transformed", expected_out_const.value());
auto const_with_value_type_check = check_expected_type_helper<std::string, Error>(expected_out_const);
CHECK_TRUE(const_with_value_type_check);
auto unexpected_out = expected_error.transform([](Value v) {
auto s = v.v.append("_transformed");
return s;
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<std::string,Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_transform_move_constructor) {
ExpectedM expected = {ValueM("transform_with_value")};
ExpectedM expected_error = ExpectedM(UnexpectedM(ErrorM("transform_with_error")));
auto expected_out = etl::move(expected).transform([](ValueM v) {
auto s = v.v.append("_transformed");
return etl::move(s);
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("transform_with_value_transformed", expected_out.value());
auto with_value_type_check = check_expected_type_helper<std::string, ErrorM>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = etl::move(expected_error).transform([](ValueM v) {
auto s = v.v.append("_transformed");
return etl::move(s);
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<std::string,ErrorM>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_transform_void) {
ExpectedV expected;
ExpectedV expected_error = {Unexpected(Error("transform_with_error"))};
auto expected_out = expected.transform([]() {
std::string s("_transformed");
return s;
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("_transformed", expected_out.value());
auto with_value_type_check = check_expected_type_helper<std::string, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = expected_error.transform([]() {
std::string s("_transformed");
return s;
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<std::string,Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_with_error", unexpected_out.error().e);
}
TEST(test_transform_void_move) {
ExpectedVM expected;
ExpectedVM expected_error = {UnexpectedM(ErrorM("transform_with_error"))};
auto expected_out = etl::move(expected).transform([]() {
std::string s("_transformed");
return s;
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("_transformed", expected_out.value());
auto with_value_type_check = check_expected_type_helper<std::string, ErrorM>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = etl::move(expected_error).transform([]() {
std::string s("_transformed");
return s;
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<std::string,ErrorM>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_transform_to_void) {
Expected expected {Value("transform_to_void")};
bool executed {false};
auto expected_out = expected.transform([&executed](Value v) {
(void) v;
executed = true;
CHECK_EQUAL("transform_to_void", v.v);
return;
});
auto to_void_type_check = check_expected_type_helper<void, Error>(expected_out);
CHECK_TRUE(to_void_type_check);
CHECK_TRUE(expected_out.has_value());
}
//*************************************************************************
TEST(test_and_then) {
Expected expected = {Value("and_then_with_value")};
Expected expected_error = {Unexpected(Error("and_then_with_error"))};
const Expected expected_const = {Value("const_and_then_with_value")};
auto expected_out = expected.and_then([](Value v) -> Expected {
return Value(v.v.append("_and_thened"));
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("and_then_with_value_and_thened", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<Value, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
auto expected_out_const = expected_const.and_then([](const Value& v) -> Expected {
auto s = v;
return Value(s.v.append("_and_thened"));
});
CHECK_TRUE(expected_out_const.has_value());
CHECK_EQUAL("const_and_then_with_value_and_thened", expected_out_const.value().v);
auto const_with_value_type_check = check_expected_type_helper<Value, Error>(expected_out_const);
CHECK_TRUE(const_with_value_type_check);
auto unexpected_out = expected_error.and_then([](Value v) -> Expected {
return Value(v.v.append("_and_thened"));
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<Value,Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("and_then_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_and_then_move_constructor) {
ExpectedM expected = ExpectedM(ValueM("and_then_with_value"));
ExpectedM expected_error = ExpectedM(UnexpectedM(ErrorM("and_then_with_error")));
auto expected_out = etl::move(expected).and_then([](ValueM v) -> ExpectedM {
return ValueM(etl::move(v.v.append("_and_thened")));
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("and_then_with_value_and_thened", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<ValueM, ErrorM>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = etl::move(expected_error).and_then([](ValueM&& v) -> ExpectedM {
return ValueM(v.v.append("_and_thened"));
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<ValueM,ErrorM>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("and_then_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_and_then_void) {
ExpectedV expected;
ExpectedV expected_error = {Unexpected(Error("and_then_with_error"))};
auto and_thened {false};
auto expected_out = expected.and_then([&and_thened]() -> ExpectedV {
and_thened = true;
return ExpectedV();
});
CHECK_TRUE(and_thened);
CHECK_TRUE(expected_out.has_value());
auto with_value_type_check = check_expected_type_helper<void, Error>(expected_out);
CHECK_TRUE(with_value_type_check);
and_thened = false;
auto unexpected_out = expected_error.and_then([&and_thened]() -> ExpectedV {
and_thened = true;
return ExpectedV();
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<void,Error>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("and_then_with_error", unexpected_out.error().e);
}
TEST(test_and_then_void_move) {
ExpectedVM expected;
ExpectedVM expected_error = {UnexpectedM(ErrorM("and_then_with_error"))};
auto and_thened {false};
auto expected_out = etl::move(expected).and_then([&and_thened]() -> ExpectedVM {
and_thened = true;
return ExpectedVM();
});
CHECK_TRUE(and_thened);
CHECK_TRUE(expected_out.has_value());
auto with_value_type_check = check_expected_type_helper<void, ErrorM>(expected_out);
CHECK_TRUE(with_value_type_check);
and_thened = false;
auto unexpected_out = etl::move(expected_error).and_then([&and_thened]() -> ExpectedVM {
and_thened = true;
return ExpectedVM();
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<void,ErrorM>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("and_then_with_error", unexpected_out.error().e);
}
//*************************************************************************
TEST(test_transform_error) {
Expected expected = {Value("transform_error_with_value")};
Expected expected_error = {Unexpected(Error("transform_error_with_error"))};
auto expected_out = expected.transform_error([](Error e) {
auto s = e.e.append("_transformed");
return s;
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("transform_error_with_value", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<Value, std::string>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = expected_error.transform_error([](Error e) {
std::string s = e.e.append("_transformed");
return s;
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<Value,std::string>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_error_with_error_transformed", unexpected_out.error());
}
//*************************************************************************
TEST(test_transform_error_move_constructor) {
ExpectedM expected = ExpectedM(ValueM("transform_error_with_value"));
ExpectedM expected_error = ExpectedM(UnexpectedM(ErrorM("transform_error_with_error")));
auto expected_out = etl::move(expected).transform_error([](ErrorM e) {
auto s = e.e.append("_transformed");
return s;
});
CHECK_TRUE(expected_out.has_value());
CHECK_EQUAL("transform_error_with_value", expected_out.value().v);
auto with_value_type_check = check_expected_type_helper<ValueM, std::string>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = etl::move(expected_error).transform_error([](ErrorM e) {
std::string s = e.e.append("_transformed");
return s;
});
CHECK_FALSE(unexpected_out.has_value());
auto with_error_type_check = check_expected_type_helper<ValueM,std::string>(unexpected_out);
CHECK_TRUE(with_error_type_check);
CHECK_EQUAL("transform_error_with_error_transformed", unexpected_out.error());
}
TEST(test_transform_error_const_rvalue) {
bool error_generated {false};
auto temp_expected = Expected(Unexpected(Error("temp_const_error")));
auto unexpected_out = static_cast<const Expected&&>(temp_expected)
.transform_error([&error_generated](const Error& e) -> std::string {
error_generated = true;
CHECK_EQUAL("temp_const_error", e.e);
return e.e;
});
CHECK_TRUE(error_generated);
CHECK_EQUAL("temp_const_error", unexpected_out.error());
}
TEST(test_transform_error_void_value) {
ExpectedV expected;
ExpectedV expected_error = UnexpectedV(Error("transform_error_void_value"));
bool executed {false};
auto expected_out = expected.transform_error([&executed](const Error& e) {
executed = true;
return e.e;
});
CHECK_FALSE(executed);
CHECK_TRUE(expected_out.has_value());
auto with_value_type_check = check_expected_type_helper<void, std::string>(expected_out);
CHECK_TRUE(with_value_type_check);
auto unexpected_out = expected_error.transform_error([&executed](const Error& e) {
executed = true;
auto s = e.e;
return s.append("_transformed");
});
CHECK_TRUE(executed);
CHECK_EQUAL("transform_error_void_value_transformed", unexpected_out.error());
auto with_error_type_check = check_expected_type_helper<void, std::string>(unexpected_out);
CHECK_TRUE(with_error_type_check);
}
};
}

184
test/test_invoke.cpp Normal file
View File

@ -0,0 +1,184 @@
/******************************************************************************
The MIT License(MIT)
Embedded Template Library.
https://github.com/ETLCPP/etl
https://www.etlcpp.com
Copyright(c) 2025 John Wellbelove
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files(the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and / or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions :
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
******************************************************************************/
#include "unit_test_framework.h"
#include "etl/invoke.h"
#include <etl/type_traits.h>
namespace
{
struct TestClass
{
int member_obj = 42;
std::string member_str = "hello";
int get_int()
{
return member_obj;
}
int const_get_int() const
{
return member_obj + 10;
}
void set_int(int v)
{
member_obj = v;
}
static int static_func(int x)
{
return x * 2;
}
};
int standalone_func(int a, int b)
{
return a + b;
}
struct Functor
{
int operator()(int x) const
{
return x * 5;
}
};
SUITE(test_invoke)
{
//*************************************************************************
TEST(test_type_traits_functions)
{
CHECK_TRUE(etl::is_function<int(int)>::value);
CHECK_TRUE(!etl::is_function<int>::value);
CHECK_TRUE(!etl::is_function<int*>::value);
CHECK_TRUE(!etl::is_function<int&>::value);
CHECK_TRUE(!etl::is_function<int&&>::value);
CHECK_TRUE(!etl::logical_not_t<etl::true_type>::value);
CHECK_TRUE(etl::logical_not_t<etl::false_type>::value);
}
//*************************************************************************
TEST(test_type_traits_member_pointers)
{
using MemObjPtr = int TestClass::*;
using MemFnPtr = int (TestClass::*)();
CHECK_TRUE(etl::is_member_pointer<MemObjPtr>::value);
CHECK_TRUE(etl::is_member_pointer<MemFnPtr>::value);
CHECK_TRUE(!etl::is_member_pointer<int*>::value);
CHECK_TRUE(etl::is_member_object_pointer<MemObjPtr>::value);
CHECK_TRUE(!etl::is_member_object_pointer<MemFnPtr>::value);
CHECK_TRUE(!etl::is_member_function_pointer<MemObjPtr>::value);
CHECK_TRUE(etl::is_member_function_pointer<MemFnPtr>::value);
}
//*************************************************************************
TEST(test_invoke_callable)
{
CHECK_EQUAL(30, etl::invoke(standalone_func, 10, 20));
auto lambda = [](int x)
{ return x * 3; };
CHECK_EQUAL(15, etl::invoke(lambda, 5));
Functor f;
CHECK_EQUAL(60, etl::invoke(f, 12));
CHECK_EQUAL(8, etl::invoke(TestClass::static_func, 4));
}
//*************************************************************************
TEST(test_invoke_mem_func_ptr)
{
TestClass obj;
TestClass* ptr = &obj;
const TestClass const_obj;
CHECK_EQUAL(42, etl::invoke(&TestClass::get_int, obj));
CHECK_EQUAL(42, etl::invoke(&TestClass::get_int, ptr));
CHECK_EQUAL(52, etl::invoke(&TestClass::const_get_int, const_obj));
etl::invoke(&TestClass::set_int, obj, 99);
CHECK_EQUAL(99, obj.member_obj);
etl::invoke(&TestClass::set_int, ptr, 101);
CHECK_EQUAL(101, ptr->member_obj);
}
//*************************************************************************
TEST(test_invoke_mem_obj_ptr)
{
TestClass obj;
TestClass* ptr = &obj;
CHECK_EQUAL(42, etl::invoke(&TestClass::member_obj, obj));
CHECK_EQUAL(42, etl::invoke(&TestClass::member_obj, ptr));
CHECK_EQUAL("hello", etl::invoke(&TestClass::member_str, obj));
etl::invoke(&TestClass::member_obj, obj) = 1000;
CHECK_EQUAL(1000, obj.member_obj);
etl::invoke(&TestClass::member_obj, ptr) = 2000;
CHECK_EQUAL(2000, ptr->member_obj);
}
//*************************************************************************
TEST(test_invoke_result_t)
{
using MemFnPtr = decltype(&TestClass::get_int);
using ConstMemFnPtr = decltype(&TestClass::const_get_int);
using MemObjPtr_int = decltype(&TestClass::member_obj);
using FnPtr = int (*)(int, int);
auto val = etl::is_same<etl::invoke_result_t<FnPtr, int, int>, int>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<MemFnPtr, TestClass&>, int>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<MemFnPtr, TestClass*>, int>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<ConstMemFnPtr, const TestClass&>, int>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<MemObjPtr_int, TestClass&>, int&>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<MemObjPtr_int, const TestClass&>, const int&>::value;
CHECK_TRUE(val);
val = etl::is_same<etl::invoke_result_t<MemObjPtr_int, TestClass*>, int&>::value;
CHECK_TRUE(val);
}
}
}