For optional monadic operations, add docs, tests, and complete type (#1568)

This commit is contained in:
Roland Reichwein 2026-08-27 13:17:43 +02:00 committed by GitHub
parent bfa29188c1
commit 74a95cf360
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 226 additions and 0 deletions

View File

@ -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 <typename F>
etl::optional<U> transform(F&& f)
```
**Description**
If `*this` contains a value, returns an `etl::optional<U>` containing the result of invoking `f`
with the contained value, otherwise returns an empty `etl::optional<U>`.
`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 <typename F>
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 <typename F>
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<int> opt = 6;
etl::optional<double> result = opt.and_then([](int i) { return i > 0 ? etl::optional<int>(i * 2) : etl::optional<int>(); })
.or_else([]() { return etl::optional<int>(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 <typename T>
struct etl::is_optional;
template <typename T>
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.

View File

@ -50,6 +50,11 @@ namespace etl
template <typename T>
class optional;
//*****************************************************************************
/// Determines if a type is an etl::optional.
/// Ignores any cv qualification on the type being tested.
///\ingroup utilities
//*****************************************************************************
template <typename T>
struct is_optional : etl::false_type
{
@ -60,6 +65,26 @@ namespace etl
{
};
template <typename T>
struct is_optional<const optional<T> > : etl::true_type
{
};
template <typename T>
struct is_optional<volatile optional<T> > : etl::true_type
{
};
template <typename T>
struct is_optional<const volatile optional<T> > : etl::true_type
{
};
#if ETL_USING_CPP17
template <typename T>
inline constexpr bool is_optional_v = etl::is_optional<T>::value;
#endif
//*****************************************************************************
/// A null option type.
///\ingroup utilities

View File

@ -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<etl::optional<int>>::value));
CHECK_TRUE((etl::is_optional<const etl::optional<int>>::value));
CHECK_TRUE((etl::is_optional<volatile etl::optional<int>>::value));
CHECK_TRUE((etl::is_optional<const volatile etl::optional<int>>::value));
CHECK_TRUE((etl::is_optional<etl::optional<Data>>::value));
CHECK_FALSE((etl::is_optional<int>::value));
CHECK_FALSE((etl::is_optional<Data>::value));
CHECK_FALSE((etl::is_optional<etl::optional<int>*>::value));
CHECK_FALSE((etl::is_optional<etl::optional<int>&>::value));
#if ETL_USING_CPP17
CHECK_TRUE((etl::is_optional_v<etl::optional<int>>));
CHECK_TRUE((etl::is_optional_v<const etl::optional<int>>));
CHECK_FALSE((etl::is_optional_v<int>));
#endif
}
//*************************************************************************
TEST(test_transform_const_rvalue)
{
const etl::optional<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> 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<int> 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<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> incremented = etl::move(engaged).and_then([](const int&& i) { return etl::optional<int>(i + 1); });
CHECK_TRUE(incremented.has_value());
CHECK_EQUAL(43, incremented.value());
etl::optional<int> from_empty = etl::move(empty).and_then([](const int&& i) { return etl::optional<int>(i + 1); });
CHECK_FALSE(from_empty.has_value());
}
//*************************************************************************
TEST(test_or_else_const_rvalue)
{
const etl::optional<int> engaged(42);
const etl::optional<int> empty;
etl::optional<int> from_engaged = etl::move(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 = etl::move(empty).or_else([]() { return etl::optional<int>(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<int> engaged(42);
auto result = engaged.transform([](int& i) -> int& { return i; });
CHECK_TRUE((etl::is_same<etl::optional<int>, 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<int> engaged(42);
etl::optional<int> empty;
auto f = [](int i) -> etl::optional<int>&
{
static etl::optional<int> shared;
shared = i + 1;
return shared;
};
auto result = engaged.and_then(f);
CHECK_TRUE((etl::is_same<etl::optional<int>, 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<int> engaged(42);
etl::optional<int> empty;
auto f = []() -> etl::optional<int>&
{
static etl::optional<int> shared(99);
return shared;
};
auto from_engaged = engaged.or_else(f);
CHECK_TRUE((etl::is_same<etl::optional<int>, 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