From 14f675df080958a54e5618819386dde5cd79dce2 Mon Sep 17 00:00:00 2001 From: David Fyfe Date: Mon, 13 Jul 2026 05:58:57 +1000 Subject: [PATCH] Topic/optional monadic operations * topic/optional-monadic-operations: - added and_then, or_else, and transform to etl::optional with unit tests - added is_optional to optional.h, used in and_then and or_else to ensure that the returned type is an optional - implemented as ref-qualified overloads with C++11 compatible trailing return types, following the etl::expected implementation --- include/etl/optional.h | 129 +++++++++++++++++++++++++ test/test_optional.cpp | 213 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 342 insertions(+) diff --git a/include/etl/optional.h b/include/etl/optional.h index 371c9f2a..3b3ed50b 100644 --- a/include/etl/optional.h +++ b/include/etl/optional.h @@ -36,6 +36,7 @@ SOFTWARE. #include "error_handler.h" #include "exception.h" #include "initializer_list.h" +#include "invoke.h" #include "memory.h" #include "placement_new.h" #include "type_traits.h" @@ -49,6 +50,16 @@ namespace etl template class optional; + template + struct is_optional : etl::false_type + { + }; + + template + struct is_optional > : etl::true_type + { + }; + //***************************************************************************** /// A null option type. ///\ingroup utilities @@ -1519,6 +1530,124 @@ namespace etl { return this->has_value() ? this->operator->() + 1 : ETL_NULLPTR; } + +#if ETL_USING_CPP11 + template ::type>::type> + auto transform(F&& f) & -> etl::optional + { + return transform_impl(etl::forward(f), *this); + } + + template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::type>::type> + auto transform(F&& f) const& -> etl::optional + { + return transform_impl(etl::forward(f), *this); + } + + template ::type>::type> + auto transform(F&& f) && -> etl::optional + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::type>::type> + auto transform(F&& f) const&& -> etl::optional + { + 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 < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::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 < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::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 < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::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 < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result::type>::type> + auto or_else(F&& f) const&& -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + private: + + template < typename F, typename TOpt, typename TRet, typename TValueRef, typename = typename etl::enable_if::value>::type> + auto transform_impl(F&& f, TOpt&& opt) const -> etl::optional + { + if (opt.has_value()) + { + return etl::optional(etl::invoke(etl::forward(f), etl::forward(*opt))); + } + else + { + return etl::optional(); + } + } + + template < typename F, typename TOpt, typename TRet, typename TValueRef, + typename = typename etl::enable_if::value && etl::is_optional::value>::type> + auto and_then_impl(F&& f, TOpt&& opt) const -> TRet + { + if (opt.has_value()) + { + return etl::invoke(etl::forward(f), etl::forward(*opt)); + } + else + { + return TRet(); + } + } + + template < typename F, typename TOpt, typename TRet, + typename = typename etl::enable_if< !etl::is_void::value && etl::is_optional::value + && etl::is_same::value>::type> + auto or_else_impl(F&& f, TOpt&& opt) const -> TRet + { + if (opt.has_value()) + { + return TRet(etl::forward(opt)); + } + else + { + return etl::invoke(etl::forward(f)); + } + } +#endif }; #include "private/diagnostic_uninitialized_push.h" diff --git a/test/test_optional.cpp b/test/test_optional.cpp index e1e05f9c..9d95f8a1 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -1603,5 +1603,218 @@ namespace #endif } #endif + +#if ETL_USING_CPP11 + //************************************************************************* + TEST(test_transform) + { + etl::optional engaged(42); + etl::optional empty; + + etl::optional doubled = engaged.transform([](int i) { return i * 2; }); + CHECK_TRUE(doubled.has_value()); + CHECK_EQUAL(84, doubled.value()); + + etl::optional from_empty = empty.transform([](int i) { return i * 2; }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_transform_const) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional doubled = engaged.transform([](int i) { return i * 2; }); + CHECK_TRUE(doubled.has_value()); + CHECK_EQUAL(84, doubled.value()); + + etl::optional from_empty = empty.transform([](int i) { return i * 2; }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_transform_change_type) + { + etl::optional engaged(42); + etl::optional empty; + + etl::optional text = engaged.transform([](int) { return std::string("forty two"); }); + CHECK_TRUE(text.has_value()); + CHECK_EQUAL("forty two", text.value()); + + etl::optional text_from_empty = empty.transform([](int) { return std::string("forty two"); }); + CHECK_FALSE(text_from_empty.has_value()); + } + + //************************************************************************* + TEST(test_transform_non_pod) + { + etl::optional engaged(Data("transform")); + etl::optional empty; + + etl::optional length = engaged.transform([](const Data& d) { return d.value.size(); }); + CHECK_TRUE(length.has_value()); + CHECK_EQUAL(9U, length.value()); + + etl::optional length_from_empty = empty.transform([](const Data& d) { return d.value.size(); }); + CHECK_FALSE(length_from_empty.has_value()); + } + + //************************************************************************* + TEST(test_transform_move) + { + etl::optional engaged(DataM(42U)); + + etl::optional moved = etl::move(engaged).transform([](DataM&& d) { return DataM(etl::move(d)); }); + CHECK_TRUE(moved.has_value()); + CHECK_TRUE(moved.value().valid); + CHECK_EQUAL(42U, moved.value().value); + CHECK_FALSE(engaged.value().valid); + + etl::optional empty; + etl::optional from_empty = etl::move(empty).transform([](DataM&& d) { return DataM(etl::move(d)); }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_and_then) + { + etl::optional engaged(42); + etl::optional empty; + + etl::optional incremented = engaged.and_then([](int i) { return etl::optional(i + 1); }); + CHECK_TRUE(incremented.has_value()); + CHECK_EQUAL(43, incremented.value()); + + etl::optional from_empty = empty.and_then([](int i) { return etl::optional(i + 1); }); + CHECK_FALSE(from_empty.has_value()); + + etl::optional rejected = engaged.and_then([](int) { return etl::optional(); }); + CHECK_FALSE(rejected.has_value()); + } + + //************************************************************************* + TEST(test_and_then_const) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional incremented = engaged.and_then([](int i) { return etl::optional(i + 1); }); + CHECK_TRUE(incremented.has_value()); + CHECK_EQUAL(43, incremented.value()); + + etl::optional from_empty = empty.and_then([](int i) { return etl::optional(i + 1); }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_and_then_change_type) + { + etl::optional engaged(42); + etl::optional empty; + + etl::optional text = engaged.and_then([](int) { return etl::optional("forty two"); }); + CHECK_TRUE(text.has_value()); + CHECK_EQUAL("forty two", text.value()); + + etl::optional text_from_empty = empty.and_then([](int) { return etl::optional("forty two"); }); + CHECK_FALSE(text_from_empty.has_value()); + } + + //************************************************************************* + TEST(test_and_then_move) + { + etl::optional engaged(DataM(42U)); + + etl::optional value = etl::move(engaged).and_then([](DataM&& d) { return etl::optional(d.value); }); + CHECK_TRUE(value.has_value()); + CHECK_EQUAL(42U, value.value()); + + etl::optional empty; + etl::optional from_empty = etl::move(empty).and_then([](DataM&& d) { return etl::optional(d.value); }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_or_else) + { + etl::optional engaged(42); + etl::optional empty; + bool called = false; + + etl::optional from_engaged = engaged.or_else( + [&called]() + { + called = true; + return etl::optional(99); + }); + CHECK_TRUE(from_engaged.has_value()); + CHECK_EQUAL(42, from_engaged.value()); + CHECK_FALSE(called); + + etl::optional from_empty = empty.or_else( + [&called]() + { + called = true; + return etl::optional(99); + }); + CHECK_TRUE(from_empty.has_value()); + CHECK_EQUAL(99, from_empty.value()); + CHECK_TRUE(called); + + etl::optional still_empty = empty.or_else([]() { return etl::optional(); }); + CHECK_FALSE(still_empty.has_value()); + } + + //************************************************************************* + TEST(test_or_else_const) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional from_engaged = engaged.or_else([]() { return etl::optional(99); }); + CHECK_TRUE(from_engaged.has_value()); + CHECK_EQUAL(42, from_engaged.value()); + + etl::optional from_empty = empty.or_else([]() { return etl::optional(99); }); + CHECK_TRUE(from_empty.has_value()); + CHECK_EQUAL(99, from_empty.value()); + } + + //************************************************************************* + TEST(test_or_else_move) + { + etl::optional engaged(DataM(42U)); + + etl::optional from_engaged = etl::move(engaged).or_else([]() { return etl::optional(DataM(99U)); }); + CHECK_TRUE(from_engaged.has_value()); + CHECK_EQUAL(42U, from_engaged.value().value); + + etl::optional empty; + etl::optional from_empty = etl::move(empty).or_else([]() { return etl::optional(DataM(99U)); }); + CHECK_TRUE(from_empty.has_value()); + CHECK_EQUAL(99U, from_empty.value().value); + } + + //************************************************************************* + TEST(test_monadic_chaining) + { + etl::optional engaged(8); + etl::optional empty; + + etl::optional result = engaged.transform([](int i) { return i * 2; }) + .and_then([](int i) { return etl::optional(i + 1); }) + .or_else([]() { return etl::optional(0); }); + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(17, result.value()); + + etl::optional result_from_empty = empty.transform([](int i) { return i * 2; }) + .and_then([](int i) { return etl::optional(i + 1); }) + .or_else([]() { return etl::optional(123); }); + CHECK_TRUE(result_from_empty.has_value()); + CHECK_EQUAL(123, result_from_empty.value()); + } +#endif } } // namespace