Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I write a std::apply on a std::expected?

In C++23, given:

expected<A, string> getA(const X& x);
expected<B, string> getB(const Y& y);

C compute_all(const A& a, const B& b);

Is there a way to avoid a classic style check like:

auto a_ret = getA(x);
if (!a_ret)
  return a_ret.error();

auto b_ret = getB(y);
if (!b_ret)
  return b_ret.error();

C final_ret = compute_all(*a_ret, *b_ret);

and write something like

expected<C, string> final_ret = magic_apply(compute_all, getA(x), getB(y))

This is an idea of implementation of magic_apply but I need something more generic (perhaps using variadic templates), that allows passing to compute_all some parameter that is not an std::expected.

template<typename Func, typename A, typename B, typename Err>
auto magic_apply(Func func, const std::expected<A, Err>& a, const std::expected<B, Err>& b)
->
std::expected<decltype(func(a.value(), b.value())), Err>
{
    if(!a) {
        return std::unexpected{ a.error() };
    }
    if(!b) {
        return std::unexpected{ b.error() };
    }
    return func(a.value(), b.value());
}

Does some feature already exist in the language I could use to write this?

like image 258
Teolazza Avatar asked Dec 12 '25 01:12

Teolazza


2 Answers

A generic version can be implemented with the help of std::optional and fold-expression:

#include <expected>
#include <functional>
#include <optional>

template<typename Func, typename... Args, typename Err>
std::expected<std::invoke_result_t<Func&, const Args&...>, Err>
magic_apply(Func func, const std::expected<Args, Err>&... args)
{
  if (std::optional<Err> err;
      ([&] {
        if (!args.has_value())
          err.emplace(args.error());
        return err.has_value();
       }() || ...)
     )
    return std::unexpected{*err};

  return std::invoke(func, args.value()...);
}

Demo

like image 158
康桓瑋 Avatar answered Dec 14 '25 17:12

康桓瑋


You could implement magic_apply like this:

template<typename F, typename ...Ts> 
auto magic_apply(F && f, Ts... ts) 
{
    // construct an array<pair<bool, string>>, where the bool indicates an error, and the string the error message
    auto errs = std::to_array({ std::make_pair(ts.has_value(), ts.has_value() ? "" : ts.error()) ...});
    
    // Find an element that actually contains an error.
    auto itr = std::ranges::find_if(errs, [](const auto & pair) {
        return pair.first == false;
    });
    
    auto ret = std::expected<decltype(f(*ts...)), decltype(itr->second)>{};

    // Either we return the error
    if (itr != errs.end()) 
        ret = std::unexpected(itr->second);
    // or the result of calling the function on all the expecteds
    else ret = f(*ts...);

    return ret;
}

Here's a demo.

like image 20
cigien Avatar answered Dec 14 '25 17:12

cigien



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!