Mapping the File I/O library into the Application

To handle the File I/O library, once again we turn to custom ValueOrError converters:

// Inject custom ValueOrError conversion
OUTCOME_V2_NAMESPACE_BEGIN
namespace convert
{
  // Provide custom ValueOrError conversion from filelib::result<U>
  // into any app::outcome<T>
  template <class T, class U>  //
  struct value_or_error<app::outcome<T>, filelib::result<U>>
  {
    // True to indicate that this converter wants `result`/`outcome`
    // to NOT reject all other `result`
    static constexpr bool enable_result_inputs = true;
    // False to indicate that this converter wants `outcome` to NOT
    // reject all other `outcome`
    static constexpr bool enable_outcome_inputs = true;

    template <class X,                                                                              //
              typename = std::enable_if_t<std::is_same<filelib::result<U>, std::decay_t<X>>::value  //
                                          && std::is_constructible<T, U>::value>>                   //
    constexpr app::outcome<T> operator()(X &&src)
    {
      // Forward any successful value
      if(src.has_value())
      {
        return {std::forward<X>(src).value()};
      }

      // Synthesise a filesystem_error, exactly as if someone had
      // called src.value()
      auto &fi = src.error();
      OUTCOME_V2_NAMESPACE::try_throw_std_exception_from_error(fi.ec);  // might throw
      return {std::make_exception_ptr(                                  //
      filelib::filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec))};
    }
  };
}  // namespace convert
OUTCOME_V2_NAMESPACE_END
View this code on Github

Note that the conversion exactly duplicates the implementation of throw_as_system_error_with_payload(failure_info fi) from namespace filelib. In a production implementation, you probably ought to call that function and catch the exception it throws into a pointer, as that would be more long term maintainable.