diff --git a/docs/utilities/optional.md b/docs/utilities/optional.md index a12aac75..0c41a829 100644 --- a/docs/utilities/optional.md +++ b/docs/utilities/optional.md @@ -222,6 +222,64 @@ bool has_value() const Checks whether *this is in a valid state, i.e. whether the contained value is initialised. Returns `true` if valid, otherwise `false`. +## Monadic operations +C++11 and above. + +Each operation is supplied as four reference qualified overloads (`&`, `const&`, `&&`, `const&&`), +so that the contained value is passed to the supplied callable with the value category of `*this`. + +```cpp +template +etl::optional transform(F&& f) +``` +**Description** +If `*this` contains a value, returns an `etl::optional` containing the result of invoking `f` +with the contained value, otherwise returns an empty `etl::optional`. + +`U` is the result type of `f`, with any reference and cv qualification removed. + +### Note +Unlike `std::optional::transform`, which only removes cv qualification, a callable returning a +reference is accepted, and the referenced value is copied into the resulting optional. + +--- + +```cpp +template +U and_then(F&& f) +``` +**Description** +If `*this` contains a value, returns the result of invoking `f` with the contained value, +otherwise returns an empty `U`. + +`U` is the result type of `f`, with any reference and cv qualification removed, and must be an +`etl::optional`. The overload takes no part in overload resolution otherwise. + +--- + +```cpp +template +U or_else(F&& f) +``` +**Description** +If `*this` contains a value, returns a copy of `*this`, otherwise returns the result of invoking +`f` with no arguments. + +`U` is the result type of `f`, with any reference and cv qualification removed, and must be an +`etl::optional` whose `value_type` is `T`. The overload takes no part in overload resolution +otherwise. + +### Example + +```cpp +etl::optional opt = 6; + +etl::optional result = opt.and_then([](int i) { return i > 0 ? etl::optional(i * 2) : etl::optional(); }) + .or_else([]() { return etl::optional(1); }) + .transform([](int i) { return double(i) / 2.0; }); +// result contains 6.0 +``` + ## Non-member functions ```cpp @@ -269,3 +327,15 @@ operator >= ``` **Description** `true` if the contents of the lhs are lexicographically greater than or equal to the contents of the rhs, otherwise `false`. + +--- + +```cpp +template +struct etl::is_optional; + +template +inline constexpr bool etl::is_optional_v; // C++17 and above +``` +**Description** +`true` if `T` is an `etl::optional`, otherwise `false`. Any cv qualification on `T` is ignored. diff --git a/include/etl/optional.h b/include/etl/optional.h index e4372453..d0c10b44 100644 --- a/include/etl/optional.h +++ b/include/etl/optional.h @@ -50,6 +50,11 @@ namespace etl template class optional; + //***************************************************************************** + /// Determines if a type is an etl::optional. + /// Ignores any cv qualification on the type being tested. + ///\ingroup utilities + //***************************************************************************** template struct is_optional : etl::false_type { @@ -60,6 +65,26 @@ namespace etl { }; + template + struct is_optional > : etl::true_type + { + }; + + template + struct is_optional > : etl::true_type + { + }; + + template + struct is_optional > : etl::true_type + { + }; + +#if ETL_USING_CPP17 + template + inline constexpr bool is_optional_v = etl::is_optional::value; +#endif + //***************************************************************************** /// A null option type. ///\ingroup utilities diff --git a/test/test_optional.cpp b/test/test_optional.cpp index 9d95f8a1..a6ffc5a7 100644 --- a/test/test_optional.cpp +++ b/test/test_optional.cpp @@ -1815,6 +1815,137 @@ namespace CHECK_TRUE(result_from_empty.has_value()); CHECK_EQUAL(123, result_from_empty.value()); } + + //************************************************************************* + TEST(test_is_optional) + { + CHECK_TRUE((etl::is_optional>::value)); + CHECK_TRUE((etl::is_optional>::value)); + CHECK_TRUE((etl::is_optional>::value)); + CHECK_TRUE((etl::is_optional>::value)); + CHECK_TRUE((etl::is_optional>::value)); + + CHECK_FALSE((etl::is_optional::value)); + CHECK_FALSE((etl::is_optional::value)); + CHECK_FALSE((etl::is_optional*>::value)); + CHECK_FALSE((etl::is_optional&>::value)); + + #if ETL_USING_CPP17 + CHECK_TRUE((etl::is_optional_v>)); + CHECK_TRUE((etl::is_optional_v>)); + CHECK_FALSE((etl::is_optional_v)); + #endif + } + + //************************************************************************* + TEST(test_transform_const_rvalue) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional doubled = etl::move(engaged).transform([](const int&& i) { return i * 2; }); + CHECK_TRUE(doubled.has_value()); + CHECK_EQUAL(84, doubled.value()); + CHECK_TRUE(engaged.has_value()); + CHECK_EQUAL(42, engaged.value()); + + etl::optional from_empty = etl::move(empty).transform([](const int&& i) { return i * 2; }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_and_then_const_rvalue) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional incremented = etl::move(engaged).and_then([](const int&& i) { return etl::optional(i + 1); }); + CHECK_TRUE(incremented.has_value()); + CHECK_EQUAL(43, incremented.value()); + + etl::optional from_empty = etl::move(empty).and_then([](const int&& i) { return etl::optional(i + 1); }); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_or_else_const_rvalue) + { + const etl::optional engaged(42); + const etl::optional empty; + + etl::optional from_engaged = etl::move(engaged).or_else([]() { return etl::optional(99); }); + CHECK_TRUE(from_engaged.has_value()); + CHECK_EQUAL(42, from_engaged.value()); + + etl::optional from_empty = etl::move(empty).or_else([]() { return etl::optional(99); }); + CHECK_TRUE(from_empty.has_value()); + CHECK_EQUAL(99, from_empty.value()); + } + + //************************************************************************* + /// Unlike std::optional, a callable returning a reference is accepted. + /// The reference is stripped from the result type and the value is copied. + //************************************************************************* + TEST(test_transform_callable_returning_reference) + { + etl::optional engaged(42); + + auto result = engaged.transform([](int& i) -> int& { return i; }); + + CHECK_TRUE((etl::is_same, decltype(result)>::value)); + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(42, result.value()); + + // The result is a copy, not an alias of the source. + result.value() = 1; + CHECK_EQUAL(42, engaged.value()); + } + + //************************************************************************* + TEST(test_and_then_callable_returning_reference) + { + etl::optional engaged(42); + etl::optional empty; + + auto f = [](int i) -> etl::optional& + { + static etl::optional shared; + shared = i + 1; + return shared; + }; + + auto result = engaged.and_then(f); + + CHECK_TRUE((etl::is_same, decltype(result)>::value)); + CHECK_TRUE(result.has_value()); + CHECK_EQUAL(43, result.value()); + + auto from_empty = empty.and_then(f); + CHECK_FALSE(from_empty.has_value()); + } + + //************************************************************************* + TEST(test_or_else_callable_returning_reference) + { + etl::optional engaged(42); + etl::optional empty; + + auto f = []() -> etl::optional& + { + static etl::optional shared(99); + return shared; + }; + + auto from_engaged = engaged.or_else(f); + + CHECK_TRUE((etl::is_same, decltype(from_engaged)>::value)); + CHECK_TRUE(from_engaged.has_value()); + CHECK_EQUAL(42, from_engaged.value()); + + auto from_empty = empty.or_else(f); + CHECK_TRUE(from_empty.has_value()); + CHECK_EQUAL(99, from_empty.value()); + } #endif } } // namespace