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
This commit is contained in:
David Fyfe 2026-07-13 05:58:57 +10:00
parent 7f290365a9
commit 14f675df08
2 changed files with 342 additions and 0 deletions

View File

@ -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 <typename T>
class optional;
template <typename T>
struct is_optional : etl::false_type
{
};
template <typename T>
struct is_optional<optional<T> > : 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 <typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, T&>::type>::type>
auto transform(F&& f) & -> etl::optional<U>
{
return transform_impl<F, optional&, U, T&>(etl::forward<F>(f), *this);
}
template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, const T&>::type>::type>
auto transform(F&& f) const& -> etl::optional<U>
{
return transform_impl<F, const optional&, U, const T&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, T&&>::type>::type>
auto transform(F&& f) && -> etl::optional<U>
{
return transform_impl<F, optional&&, U, T&&>(etl::forward<F>(f), etl::move(*this));
}
template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, const T&&>::type>::type>
auto transform(F&& f) const&& -> etl::optional<U>
{
return transform_impl<F, const optional&&, U, const T&&>(etl::forward<F>(f), etl::move(*this));
}
template <typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, T&>::type>::type>
auto and_then(F&& f) & -> U
{
return and_then_impl<F, optional&, U, T&>(etl::forward<F>(f), *this);
}
template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, const T&>::type>::type>
auto and_then(F&& f) const& -> U
{
return and_then_impl<F, const optional&, U, const T&>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, T&&>::type>::type>
auto and_then(F&& f) && -> U
{
return and_then_impl<F, optional&&, U, T&&>(etl::forward<F>(f), etl::move(*this));
}
template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void, const T&&>::type>::type>
auto and_then(F&& f) const&& -> U
{
return and_then_impl<F, const optional&&, U, const T&&>(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 or_else(F&& f) & -> U
{
return or_else_impl<F, optional&, U>(etl::forward<F>(f), *this);
}
template < typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void>::type>::type>
auto or_else(F&& f) const& -> U
{
return or_else_impl<F, const optional&, U>(etl::forward<F>(f), *this);
}
template <typename F, typename U = typename etl::remove_cvref< typename etl::invoke_result<F, void>::type>::type>
auto or_else(F&& f) && -> U
{
return or_else_impl<F, optional&&, 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 or_else(F&& f) const&& -> U
{
return or_else_impl<F, const optional&&, U>(etl::forward<F>(f), etl::move(*this));
}
private:
template < typename F, typename TOpt, typename TRet, typename TValueRef, typename = typename etl::enable_if<!etl::is_void<TRet>::value>::type>
auto transform_impl(F&& f, TOpt&& opt) const -> etl::optional<TRet>
{
if (opt.has_value())
{
return etl::optional<TRet>(etl::invoke(etl::forward<F>(f), etl::forward<TValueRef>(*opt)));
}
else
{
return etl::optional<TRet>();
}
}
template < typename F, typename TOpt, typename TRet, typename TValueRef,
typename = typename etl::enable_if<!etl::is_void<TRet>::value && etl::is_optional<TRet>::value>::type>
auto and_then_impl(F&& f, TOpt&& opt) const -> TRet
{
if (opt.has_value())
{
return etl::invoke(etl::forward<F>(f), etl::forward<TValueRef>(*opt));
}
else
{
return TRet();
}
}
template < typename F, typename TOpt, typename TRet,
typename = typename etl::enable_if< !etl::is_void<TRet>::value && etl::is_optional<TRet>::value
&& etl::is_same<typename TRet::value_type, T>::value>::type>
auto or_else_impl(F&& f, TOpt&& opt) const -> TRet
{
if (opt.has_value())
{
return TRet(etl::forward<TOpt>(opt));
}
else
{
return etl::invoke(etl::forward<F>(f));
}
}
#endif
};
#include "private/diagnostic_uninitialized_push.h"

View File

@ -1603,5 +1603,218 @@ namespace
#endif
}
#endif
#if ETL_USING_CPP11
//*************************************************************************
TEST(test_transform)
{
etl::optional<int> engaged(42);
etl::optional<int> empty;
etl::optional<int> doubled = engaged.transform([](int i) { return i * 2; });
CHECK_TRUE(doubled.has_value());
CHECK_EQUAL(84, doubled.value());
etl::optional<int> from_empty = empty.transform([](int i) { return i * 2; });
CHECK_FALSE(from_empty.has_value());
}
//*************************************************************************
TEST(test_transform_const)
{
const etl::optional<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> doubled = engaged.transform([](int i) { return i * 2; });
CHECK_TRUE(doubled.has_value());
CHECK_EQUAL(84, doubled.value());
etl::optional<int> from_empty = empty.transform([](int i) { return i * 2; });
CHECK_FALSE(from_empty.has_value());
}
//*************************************************************************
TEST(test_transform_change_type)
{
etl::optional<int> engaged(42);
etl::optional<int> empty;
etl::optional<std::string> text = engaged.transform([](int) { return std::string("forty two"); });
CHECK_TRUE(text.has_value());
CHECK_EQUAL("forty two", text.value());
etl::optional<std::string> 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<Data> engaged(Data("transform"));
etl::optional<Data> empty;
etl::optional<size_t> length = engaged.transform([](const Data& d) { return d.value.size(); });
CHECK_TRUE(length.has_value());
CHECK_EQUAL(9U, length.value());
etl::optional<size_t> 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<DataM> engaged(DataM(42U));
etl::optional<DataM> 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<DataM> empty;
etl::optional<DataM> 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<int> engaged(42);
etl::optional<int> empty;
etl::optional<int> incremented = engaged.and_then([](int i) { return etl::optional<int>(i + 1); });
CHECK_TRUE(incremented.has_value());
CHECK_EQUAL(43, incremented.value());
etl::optional<int> from_empty = empty.and_then([](int i) { return etl::optional<int>(i + 1); });
CHECK_FALSE(from_empty.has_value());
etl::optional<int> rejected = engaged.and_then([](int) { return etl::optional<int>(); });
CHECK_FALSE(rejected.has_value());
}
//*************************************************************************
TEST(test_and_then_const)
{
const etl::optional<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> incremented = engaged.and_then([](int i) { return etl::optional<int>(i + 1); });
CHECK_TRUE(incremented.has_value());
CHECK_EQUAL(43, incremented.value());
etl::optional<int> from_empty = empty.and_then([](int i) { return etl::optional<int>(i + 1); });
CHECK_FALSE(from_empty.has_value());
}
//*************************************************************************
TEST(test_and_then_change_type)
{
etl::optional<int> engaged(42);
etl::optional<int> empty;
etl::optional<std::string> text = engaged.and_then([](int) { return etl::optional<std::string>("forty two"); });
CHECK_TRUE(text.has_value());
CHECK_EQUAL("forty two", text.value());
etl::optional<std::string> text_from_empty = empty.and_then([](int) { return etl::optional<std::string>("forty two"); });
CHECK_FALSE(text_from_empty.has_value());
}
//*************************************************************************
TEST(test_and_then_move)
{
etl::optional<DataM> engaged(DataM(42U));
etl::optional<uint32_t> value = etl::move(engaged).and_then([](DataM&& d) { return etl::optional<uint32_t>(d.value); });
CHECK_TRUE(value.has_value());
CHECK_EQUAL(42U, value.value());
etl::optional<DataM> empty;
etl::optional<uint32_t> from_empty = etl::move(empty).and_then([](DataM&& d) { return etl::optional<uint32_t>(d.value); });
CHECK_FALSE(from_empty.has_value());
}
//*************************************************************************
TEST(test_or_else)
{
etl::optional<int> engaged(42);
etl::optional<int> empty;
bool called = false;
etl::optional<int> from_engaged = engaged.or_else(
[&called]()
{
called = true;
return etl::optional<int>(99);
});
CHECK_TRUE(from_engaged.has_value());
CHECK_EQUAL(42, from_engaged.value());
CHECK_FALSE(called);
etl::optional<int> from_empty = empty.or_else(
[&called]()
{
called = true;
return etl::optional<int>(99);
});
CHECK_TRUE(from_empty.has_value());
CHECK_EQUAL(99, from_empty.value());
CHECK_TRUE(called);
etl::optional<int> still_empty = empty.or_else([]() { return etl::optional<int>(); });
CHECK_FALSE(still_empty.has_value());
}
//*************************************************************************
TEST(test_or_else_const)
{
const etl::optional<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> from_engaged = engaged.or_else([]() { return etl::optional<int>(99); });
CHECK_TRUE(from_engaged.has_value());
CHECK_EQUAL(42, from_engaged.value());
etl::optional<int> from_empty = empty.or_else([]() { return etl::optional<int>(99); });
CHECK_TRUE(from_empty.has_value());
CHECK_EQUAL(99, from_empty.value());
}
//*************************************************************************
TEST(test_or_else_move)
{
etl::optional<DataM> engaged(DataM(42U));
etl::optional<DataM> from_engaged = etl::move(engaged).or_else([]() { return etl::optional<DataM>(DataM(99U)); });
CHECK_TRUE(from_engaged.has_value());
CHECK_EQUAL(42U, from_engaged.value().value);
etl::optional<DataM> empty;
etl::optional<DataM> from_empty = etl::move(empty).or_else([]() { return etl::optional<DataM>(DataM(99U)); });
CHECK_TRUE(from_empty.has_value());
CHECK_EQUAL(99U, from_empty.value().value);
}
//*************************************************************************
TEST(test_monadic_chaining)
{
etl::optional<int> engaged(8);
etl::optional<int> empty;
etl::optional<int> result = engaged.transform([](int i) { return i * 2; })
.and_then([](int i) { return etl::optional<int>(i + 1); })
.or_else([]() { return etl::optional<int>(0); });
CHECK_TRUE(result.has_value());
CHECK_EQUAL(17, result.value());
etl::optional<int> result_from_empty = empty.transform([](int i) { return i * 2; })
.and_then([](int i) { return etl::optional<int>(i + 1); })
.or_else([]() { return etl::optional<int>(123); });
CHECK_TRUE(result_from_empty.has_value());
CHECK_EQUAL(123, result_from_empty.value());
}
#endif
}
} // namespace