Skip to main content
Add references to follow up post
Source Link
mahush
  • 115
  • 7

(Please note: the post about the compose implementation announced below is now available.)

This is about decorating callables by making their argument and return value to be a std::optional. Therefore I created the template function make_skippable that takes a callable and returns a wrapping lambda having an optional argument and an optional return value.

The purpose of this functionality is to allow callables to be composed as a chain, whereby in the event that a callable doesn’t produce a result (by returning a std::nullopt), the chain breaks by skipping the processing of the subsequent callables (unless the callable is able to handle a std::nullopt). I will follow up on the compose implementation later in a separate review requestthe compose implementation later in a separate review request.

This is about decorating callables by making their argument and return value to be a std::optional. Therefore I created the template function make_skippable that takes a callable and returns a wrapping lambda having an optional argument and an optional return value.

The purpose of this functionality is to allow callables to be composed as a chain, whereby in the event that a callable doesn’t produce a result (by returning a std::nullopt), the chain breaks by skipping the processing of the subsequent callables (unless the callable is able to handle a std::nullopt). I will follow up on the compose implementation later in a separate review request.

(Please note: the post about the compose implementation announced below is now available.)

This is about decorating callables by making their argument and return value to be a std::optional. Therefore I created the template function make_skippable that takes a callable and returns a wrapping lambda having an optional argument and an optional return value.

The purpose of this functionality is to allow callables to be composed as a chain, whereby in the event that a callable doesn’t produce a result (by returning a std::nullopt), the chain breaks by skipping the processing of the subsequent callables (unless the callable is able to handle a std::nullopt). I will follow up on the compose implementation later in a separate review request.

Clarified that the godbolt link just refers to the initial implementation
Source Link
mahush
  • 115
  • 7

This godbolt link refers to the latest version of the implementation shown above and does also include tests.

This godbolt link refers to the latest version of the implementation and does also include tests.

This godbolt link refers to the implementation shown above and does also include tests.

Source Link
mahush
  • 115
  • 7

Extending callable signature with std::optional in context of function composition (make_skippable)

This is about decorating callables by making their argument and return value to be a std::optional. Therefore I created the template function make_skippable that takes a callable and returns a wrapping lambda having an optional argument and an optional return value.

Here a simple example of how to use this function:

auto callable = [](auto arg) { return arg; };
auto skippable_callable = make_skippable(callable);
std::optional<int> res1 = skippable_callable(static_cast<int>(1));
std::optional<int> res2 = skippable_callable(std::optional<int>{2});
std::optional<int> res3 = skippable_callable(std::optional<int>{});

The purpose of this functionality is to allow callables to be composed as a chain, whereby in the event that a callable doesn’t produce a result (by returning a std::nullopt), the chain breaks by skipping the processing of the subsequent callables (unless the callable is able to handle a std::nullopt). I will follow up on the compose implementation later in a separate review request.

Below you will find the detailed requirements that the make_skippable implementation should fulfil:

  • wrap a callable regardless of whether it already has an optional argument or an optional return value
  • in case a nullopt argument occurs, only skip a callable that has a value argument, instead execute a callable that has an optional argument by forwarding the nullopt
  • support all kinds callables having a single argument, including generic lambdas
  • support perfect forwarding regarding argument and callable object
  • c++17

Here the implementation itself:

template <typename>
struct IsOptional : std::false_type {};
template <typename T>
struct IsOptional<std::optional<T>> : std::true_type {};

template <typename TArg>
auto ensure_optional(TArg&& arg) {
  if constexpr (IsOptional<std::decay_t<TArg>>::value) {
    return std::forward<TArg>(arg);
  } else {
    return std::make_optional(std::forward<TArg>(arg));
  }
}

template <typename TFn>
auto make_skippable(TFn&& fn) {
  return [fn = std::forward<TFn>(fn)](auto&& optional_or_value) {
    auto&& optional_arg = ensure_optional(std::forward<decltype(optional_or_value)>(optional_or_value));
    using OptionalArg = decltype(optional_arg);

    constexpr bool invoke_with_optional = std::is_invocable_v<TFn, OptionalArg>;
    if constexpr (invoke_with_optional) {
      auto&& res = fn(std::forward<OptionalArg>(optional_arg));
      return ensure_optional(std::forward<decltype(res)>(res));
    }

    const bool invoke_with_value = optional_arg.has_value();
    if (invoke_with_value) {
      auto&& value = std::forward<OptionalArg>(optional_arg).value();
      auto&& res = fn(std::forward<decltype(value)>(value));
      return ensure_optional(std::forward<decltype(res)>(res));
    }

    // skip fn
    using ValueArg = typename std::decay_t<OptionalArg>::value_type;
    using Res = typename std::invoke_result<TFn, ValueArg>::type;
    if constexpr (IsOptional<Res>::value) {
      return Res{};
    } else {
      return std::optional<Res>{};
    }
  };
}

Kudos to Jonathan Boccara who inspired me with his blog post The Optional Monad In C++, Without the Ugly Stuff - Fluent C++.

Happy to receive any kind of feedback :-)

This godbolt link refers to the latest version of the implementation and does also include tests.