From 452de461f9f133fe57dedf5bff32c9cb934ff71a Mon Sep 17 00:00:00 2001 From: whitfijs-jw <58998493+whitfijs-jw@users.noreply.github.com> Date: Sat, 13 Dec 2025 03:04:38 -0500 Subject: [PATCH] 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 - 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 to expected 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 and get with get and get --------- Co-authored-by: Jon Whitfield --- include/etl/expected.h | 340 +++++++++++++++++++++++ include/etl/invoke.h | 148 ++++++++++ test/CMakeLists.txt | 1 + test/test_expected.cpp | 612 +++++++++++++++++++++++++++++++++++++++++ test/test_invoke.cpp | 184 +++++++++++++ 5 files changed, 1285 insertions(+) create mode 100644 include/etl/invoke.h create mode 100644 test/test_invoke.cpp diff --git a/include/etl/expected.h b/include/etl/expected.h index 71c9d303..948df290 100644 --- a/include/etl/expected.h +++ b/include/etl/expected.h @@ -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 class expected; + + template + struct is_expected : etl::false_type {}; + + template + struct is_expected> : etl::true_type {}; + //*************************************************************************** /// Base exception for et::expected //*************************************************************************** @@ -732,6 +743,104 @@ namespace etl } #endif +#if ETL_USING_CPP11 + template ::type>::type> + auto transform(F&& f) & -> expected + { + return transform_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform(F&& f) const& -> expected + { + return transform_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform(F&& f) && -> expected + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto transform(F&& f) const&& -> expected + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto and_then(F&& f) & -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto and_then(F&& f) const& -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto and_then(F&& f) && -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto and_then(F&& f) const&& -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto or_else(F&& f) & -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto or_else(F&& f) const & -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto or_else(F&& f) && -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto or_else(F&& f) const && -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto transform_error(F&& f) & -> expected + { + return transform_error_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform_error(F&& f) const & -> expected + { + return transform_error_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform_error(F&& f) && -> expected + { + return transform_error_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto transform_error(F&& f) const&& -> expected + { + return transform_error_impl(etl::forward(f), etl::move(*this)); + } +#endif + private: enum @@ -743,6 +852,72 @@ namespace etl typedef etl::variant storage_type; storage_type storage; + + template ::value>::type> + auto transform_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + return expected(etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage)))); + } + else + { + return expected(unexpected(etl::forward(exp).error())); + } + } + + template ::value>::type> + auto transform_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage))); + return expected(); + } + else + { + return expected(unexpected(etl::forward(exp).error())); + } + } + + template ::value && etl::is_expected::value && etl::is_same::value>::type> + auto and_then_impl(F&& f, TExp&& exp) const -> TRet + { + if (exp.has_value()) + { + return etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage))); + } + else + { + return TRet(unexpected(etl::forward(exp).error())); + } + } + + template ::value && etl::is_expected::value && etl::is_same::value>::type> + auto or_else_impl(F&& f, TExp&& exp) const -> TRet + { + if (exp.has_value()) + { + return TRet(etl::forward(exp).value()); + } + else + { + return etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage))); + } + } + + template ::value>::type> + auto transform_error_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + return expected(etl::forward(exp).value()); + } + else + { + return expected(unexpected(etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage))))); + } + } }; //***************************************************************************** @@ -942,6 +1117,105 @@ namespace etl swap(storage, other.storage); } +#if ETL_USING_CPP11 + template::type>::type> + auto transform(F&& f) & -> expected + { + return transform_impl(etl::forward(f), *this); + } + + template::type>::type> + auto transform(F&& f) const & -> expected + { + return transform_impl(etl::forward(f), *this); + } + + template::type>::type> + auto transform(F&& f) && -> expected + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template::type>::type> + auto transform(F&& f) const && -> expected + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template::type>::type> + auto and_then(F&& f) & -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template::type>::type> + auto and_then(F&& f) const & -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template::type>::type> + auto and_then(F&& f) && -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template::type>::type> + auto and_then(F&& f) const && -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto or_else(F&& f) & -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto or_else(F&& f) const & -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto or_else(F&& f) && -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto or_else(F&& f) const && -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto transform_error(F&& f) & -> expected + { + return transform_error_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform_error(F&& f) const & -> expected + { + return transform_error_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform_error(F&& f) && -> expected + { + return transform_error_impl(etl::forward(f), etl::move(*this)); + } + + template ::type>::type> + auto transform_error(F&& f) const && -> expected + { + return transform_error_impl(etl::forward(f), *this); + } +#endif + + private: enum @@ -951,6 +1225,72 @@ namespace etl }; etl::variant storage; + + template ::value>::type> + auto transform_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + return expected(etl::invoke(etl::forward(f))); + } + else + { + return expected(unexpected(etl::forward(exp).error())); + } + } + + template ::value>::type> + auto transform_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + etl::invoke(etl::forward(f)); + return expected(); + } + else + { + return expected(unexpected(etl::forward(exp).error())); + } + } + + template ::value && etl::is_expected::value && etl::is_same::value>::type> + auto and_then_impl(F&& f, TExp&& exp) const -> TRet + { + if (exp.has_value()) + { + return etl::invoke(etl::forward(f)); + } + else + { + return TRet(unexpected(etl::forward(exp).error())); + } + } + + template ::value && etl::is_expected::value && etl::is_same::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), etl::forward(etl::get(exp.storage))); + } + } + + template ::value>::type> + auto transform_error_impl(F&& f, TExp&& exp) const -> expected + { + if (exp.has_value()) + { + return expected(); + } + else + { + return expected(unexpected(etl::invoke(etl::forward(f), etl::forward(etl::get(exp.storage))))); + } + } }; } diff --git a/include/etl/invoke.h b/include/etl/invoke.h new file mode 100644 index 00000000..67606e26 --- /dev/null +++ b/include/etl/invoke.h @@ -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 struct logical_not_t : etl::integral_constant {}; + + /// is T a function -- a function cannot be const qualified like a variable + template struct is_function : public etl::integral_constant::value> { }; + template struct is_function : public etl::false_type { }; + template struct is_function : public etl::false_type { }; + + /// is T a member pointer + template struct is_member_pointer_helper : etl::false_type {}; + template struct is_member_pointer_helper : etl::true_type {}; + template struct is_member_pointer : is_member_pointer_helper> {}; + + /// is T a member function pointer + template struct is_member_function_pointer_helper : etl::false_type {}; + template struct is_member_function_pointer_helper : public etl::is_function::type {}; + template struct is_member_function_pointer : public is_member_function_pointer_helper>::type {}; + + /// is T a member object pointer + template struct is_member_object_pointer_helper : public etl::false_type { }; + template struct is_member_object_pointer_helper : public logical_not_t>::type { }; + template struct is_member_object_pointer : public is_member_object_pointer_helper>::type {}; + + template < + typename F, + typename ... TArgs, + typename = typename etl::enable_if< + !etl::is_member_pointer>::value>::type + > + ETL_CONSTEXPR auto invoke(F&& f, TArgs&& ... args) + -> decltype(etl::forward(f)(etl::forward(args)...)) { + return etl::forward(f)(etl::forward(args)...); + } + + template < + typename F, + typename T, + typename... TArgs, + typename = typename etl::enable_if< + etl::is_member_function_pointer>::value && + !etl::is_pointer>::value + >::type + > + ETL_CONSTEXPR auto invoke(F&& f, T&& t, TArgs&&... args) + -> decltype((etl::forward(t).*f)(etl::forward(args)...)) + { + return (etl::forward(t).*f)(etl::forward(args)...); + } + + template < + typename F, + typename T, + typename ... TArgs, + typename = typename etl::enable_if< + etl::is_member_function_pointer>::value && + etl::is_pointer>::value + >::type + > + ETL_CONSTEXPR auto invoke(F&& f, T&& t, TArgs&&... args) + -> decltype(((*etl::forward(t)).*f)(etl::forward(args)...)) + { + return ((*etl::forward(t)).*f)(etl::forward(args)...); + } + + template < + typename F, + typename T, + typename = typename etl::enable_if< + etl::is_member_object_pointer>::value && + !etl::is_pointer>::value + >::type + > + ETL_CONSTEXPR auto invoke(F&& f, T&& t) + -> decltype(etl::forward(t).*f) + { + return etl::forward(t).*f; + } + + template < + typename F, + typename T, + typename = typename etl::enable_if< + etl::is_member_object_pointer>::value && + etl::is_pointer>::value + >::type + > + ETL_CONSTEXPR auto invoke(F&& f, T&& t) + -> decltype(((*etl::forward(t)).*f)) + { + return ((*etl::forward(t)).*f); + } + + template struct invoke_result; + + template + struct invoke_result< + F, + etl::void_t(), etl::declval()...))>, + Us... + > { + using type = decltype(etl::invoke(etl::declval(), etl::declval()...)); + }; + + template + using invoke_result_t = typename invoke_result::type; + +} + +#endif diff --git a/test/CMakeLists.txt b/test/CMakeLists.txt index 624f38d2..2371b324 100644 --- a/test/CMakeLists.txt +++ b/test/CMakeLists.txt @@ -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 diff --git a/test/test_expected.cpp b/test/test_expected.cpp index d7df3577..deb7502b 100644 --- a/test/test_expected.cpp +++ b/test/test_expected.cpp @@ -792,5 +792,617 @@ namespace CHECK_TRUE(test_unexp_1_swap == test_unexp_2); CHECK_TRUE(test_unexp_2_swap == test_unexp_1); } + + //************************************************************************* + template + struct value_type_helper { + static bool check(TExpected& expected) { + return etl::is_same< + typename etl::decay::type, + TValue + >::value; + } + }; + + template + struct value_type_helper::value>::type> { + static bool check(TExpected& expected) { + (void)expected; + return true; + } + }; + + template + bool check_expected_type_helper(TExpected& expected) { + + bool value_type_ok = value_type_helper::check(expected); + + bool error_type_ok = etl::is_same< + typename etl::decay::type, + TError + >::value; + + bool expected_type_ok = etl::is_same< + typename etl::decay::type, + etl::expected + >::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(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(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(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(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(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(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(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 { + return etl::unexpected(e.e.append("_to_string")); + }); + + auto with_error_type_check = check_expected_type_helper(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 { + return etl::unexpected(e.e.append("_to_string")); + }); + + auto with_error_type_check = check_expected_type_helper(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(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(unexpected_out); + CHECK_TRUE(with_error_type_check); + + } }; } diff --git a/test/test_invoke.cpp b/test/test_invoke.cpp new file mode 100644 index 00000000..4c06d926 --- /dev/null +++ b/test/test_invoke.cpp @@ -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 + +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::value); + CHECK_TRUE(!etl::is_function::value); + CHECK_TRUE(!etl::is_function::value); + CHECK_TRUE(!etl::is_function::value); + CHECK_TRUE(!etl::is_function::value); + CHECK_TRUE(!etl::logical_not_t::value); + CHECK_TRUE(etl::logical_not_t::value); + } + + //************************************************************************* + TEST(test_type_traits_member_pointers) + { + using MemObjPtr = int TestClass::*; + using MemFnPtr = int (TestClass::*)(); + + CHECK_TRUE(etl::is_member_pointer::value); + CHECK_TRUE(etl::is_member_pointer::value); + CHECK_TRUE(!etl::is_member_pointer::value); + + CHECK_TRUE(etl::is_member_object_pointer::value); + CHECK_TRUE(!etl::is_member_object_pointer::value); + + CHECK_TRUE(!etl::is_member_function_pointer::value); + CHECK_TRUE(etl::is_member_function_pointer::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, int>::value; + CHECK_TRUE(val); + + val = etl::is_same, int>::value; + CHECK_TRUE(val); + + val = etl::is_same, int>::value; + CHECK_TRUE(val); + + val = etl::is_same, int>::value; + CHECK_TRUE(val); + + val = etl::is_same, int&>::value; + CHECK_TRUE(val); + + val = etl::is_same, const int&>::value; + CHECK_TRUE(val); + + val = etl::is_same, int&>::value; + CHECK_TRUE(val); + } + } +} \ No newline at end of file