diff --git a/include/etl/optional.h b/include/etl/optional.h index eeb4b5c7..e4372453 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 @@ -174,10 +185,9 @@ namespace etl /// Constructs T in-place from U&&, without requiring T to be /// copy/move constructible. //*************************************************************************** - template ::value && !etl::is_same::type, etl::in_place_t>::value - && !etl::is_same::type, optional_impl>::value, - int>::type = 0> + template ::value && !etl::is_same, etl::in_place_t>::value + && !etl::is_same, optional_impl>::value, + int> = 0> ETL_CONSTEXPR20_STL optional_impl(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { storage.construct(etl::forward(value_)); @@ -276,9 +286,7 @@ namespace etl /// Assignment operator from value type. //*************************************************************************** #if ETL_USING_CPP11 - template ::value && !etl::is_same::type, optional_impl>::value, - int>::type = 0> + template ::value && !etl::is_same, optional_impl>::value, int> = 0> ETL_CONSTEXPR20_STL optional_impl& operator=(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { storage.construct(etl::forward(value_)); @@ -511,8 +519,7 @@ namespace etl //******************************************* /// Emplaces a value from initializer_list and arbitrary constructor arguments. //******************************************* - template &, Args...>::value, int>::type = 0 > + template &, Args...>::value, int> = 0 > ETL_CONSTEXPR20_STL T& emplace(std::initializer_list ilist, Args&&... args) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible&, Args...>::value)) { @@ -1217,8 +1224,8 @@ namespace etl }; } // namespace private_optional -#define ETL_OPTIONAL_ENABLE_CPP14 typename etl::enable_if< etl::is_pod::type>::value, int>::type = 0 -#define ETL_OPTIONAL_ENABLE_CPP20_STL typename etl::enable_if< !etl::is_pod::type>::value, int>::type = 0 +#define ETL_OPTIONAL_ENABLE_CPP14 etl::enable_if_t< etl::is_pod::type>::value, int> = 0 +#define ETL_OPTIONAL_ENABLE_CPP20_STL etl::enable_if_t< !etl::is_pod::type>::value, int> = 0 #define ETL_OPTIONAL_ENABLE_CONSTEXPR_BOOL_RETURN_CPP14 \ ETL_CONSTEXPR14 typename etl::enable_if< etl::is_pod::type>::value, bool>::type @@ -1319,12 +1326,10 @@ namespace etl /// Constructs T in-place from U&&, without requiring T to be /// copy/move constructible. //*************************************************************************** - template < typename U, - typename etl::enable_if< etl::is_constructible::value && !etl::is_same::type, etl::optional>::value - && !etl::is_same::type, etl::in_place_t>::value - && !etl::is_same::type, etl::nullopt_t>::value - && etl::is_pod::type>::value, - int>::type = 0> + template < typename U, etl::enable_if_t< etl::is_constructible::value && !etl::is_same, etl::optional>::value + && !etl::is_same, etl::in_place_t>::value + && !etl::is_same, etl::nullopt_t>::value && etl::is_pod>::value, + int> = 0> ETL_CONSTEXPR14 optional(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) : impl_t(etl::forward(value_)) { @@ -1334,11 +1339,10 @@ namespace etl /// Converting constructor from value type. //*************************************************************************** template < typename U, - typename etl::enable_if< etl::is_constructible::value && !etl::is_same::type, etl::optional>::value - && !etl::is_same::type, etl::in_place_t>::value - && !etl::is_same::type, etl::nullopt_t>::value - && !etl::is_pod::type>::value, - int>::type = 0> + etl::enable_if_t< etl::is_constructible::value && !etl::is_same, etl::optional>::value + && !etl::is_same, etl::in_place_t>::value && !etl::is_same, etl::nullopt_t>::value + && !etl::is_pod>::value, + int> = 0> ETL_CONSTEXPR20_STL optional(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) : impl_t(etl::forward(value_)) { @@ -1446,11 +1450,9 @@ namespace etl //*************************************************************************** /// Converting assignment operator from value type. //*************************************************************************** - template < typename U, - typename etl::enable_if< etl::is_constructible::value && !etl::is_same::type, etl::optional>::value - && !etl::is_same::type, etl::nullopt_t>::value - && etl::is_pod::type>::value, - int>::type = 0> + template < typename U, etl::enable_if_t< etl::is_constructible::value && !etl::is_same, etl::optional>::value + && !etl::is_same, etl::nullopt_t>::value && etl::is_pod>::value, + int> = 0> ETL_CONSTEXPR14 optional& operator=(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { impl_t::operator=(etl::forward(value_)); @@ -1462,10 +1464,9 @@ namespace etl /// Converting assignment operator from value type. //*************************************************************************** template < typename U, - typename etl::enable_if< etl::is_constructible::value && !etl::is_same::type, etl::optional>::value - && !etl::is_same::type, etl::nullopt_t>::value - && !etl::is_pod::type>::value, - int>::type = 0> + etl::enable_if_t< etl::is_constructible::value && !etl::is_same, etl::optional>::value + && !etl::is_same, etl::nullopt_t>::value && !etl::is_pod>::value, + int> = 0> ETL_CONSTEXPR20_STL optional& operator=(U&& value_) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { impl_t::operator=(etl::forward(value_)); @@ -1519,6 +1520,124 @@ namespace etl { return this->has_value() ? this->operator->() + 1 : ETL_NULLPTR; } + +#if ETL_USING_CPP11 + template >, etl::enable_if_t::value, int> = 0> + auto transform(F&& f) & -> etl::optional + { + return transform_impl(etl::forward(f), *this); + } + + template >, etl::enable_if_t::value, int> = 0> + auto transform(F&& f) const& -> etl::optional + { + return transform_impl(etl::forward(f), *this); + } + + template >, etl::enable_if_t::value, int> = 0> + auto transform(F&& f) && -> etl::optional + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template >, etl::enable_if_t::value, int> = 0> + auto transform(F&& f) const&& -> etl::optional + { + return transform_impl(etl::forward(f), etl::move(*this)); + } + + template >> + auto and_then(F&& f) & -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template < typename F, typename U = etl::remove_cvref_t< etl::invoke_result_t>> + auto and_then(F&& f) const& -> U + { + return and_then_impl(etl::forward(f), *this); + } + + template >> + auto and_then(F&& f) && -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template < typename F, typename U = etl::remove_cvref_t< etl::invoke_result_t>> + auto and_then(F&& f) const&& -> U + { + return and_then_impl(etl::forward(f), etl::move(*this)); + } + + template >> + auto or_else(F&& f) & -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template < typename F, typename U = etl::remove_cvref_t>> + auto or_else(F&& f) const& -> U + { + return or_else_impl(etl::forward(f), *this); + } + + template >> + auto or_else(F&& f) && -> U + { + return or_else_impl(etl::forward(f), etl::move(*this)); + } + + template < typename F, typename U = etl::remove_cvref_t< etl::invoke_result_t>> + 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 = etl::enable_if_t::value>> + 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 = etl::enable_if_t::value && etl::is_optional::value>> + 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 = etl::enable_if_t< !etl::is_void::value && etl::is_optional::value && etl::is_same::value>> + 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" @@ -2132,17 +2251,17 @@ namespace etl /// This overload participates in overload resolution only if /// `etl::is_constructible_v` is true. //*************************************************************************** - template ::value, int>::type = 0, // + template ::value, int> = 0, // typename U = T, ETL_OPTIONAL_ENABLE_CPP14> - ETL_CONSTEXPR14 etl::optional make_optional(Args&&... args) // + ETL_CONSTEXPR14 etl::optional make_optional(Args&&... args) // ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { return etl::optional(etl::in_place_t{}, etl::forward(args)...); } - template ::value, int>::type = 0, // - typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> + template ::value, int> = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> ETL_CONSTEXPR20_STL etl::optional make_optional(Args&&... args) // ETL_NOEXCEPT_IF((etl::is_nothrow_constructible::value)) { @@ -2156,17 +2275,17 @@ namespace etl /// This overload participates in overload resolution only if /// `etl::is_constructible_v&, Args...>` is true. //*************************************************************************** - template &, Args...>::value, int>::type = 0, // + template &, Args...>::value, int> = 0, // typename U = T, ETL_OPTIONAL_ENABLE_CPP14> ETL_CONSTEXPR14 etl::optional make_optional(std::initializer_list ilist, Args&&... args) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible&, Args...>::value)) { return etl::optional(etl::in_place_t{}, ilist, etl::forward(args)...); } - template &, Args...>::value, int>::type = 0, // - typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> + template &, Args...>::value, int> = 0, // + typename U = T, ETL_OPTIONAL_ENABLE_CPP20_STL> ETL_CONSTEXPR20_STL etl::optional make_optional(std::initializer_list ilist, Args&&... args) ETL_NOEXCEPT_IF((etl::is_nothrow_constructible&, Args...>::value)) { 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