Auto-throwing filesystem_error

Something not mentioned at all until now (and properly described in the next section, Default actions) is that Outcome can be programmed take various actions when the user tries to observe .value() when there is no value, and so on for the other possible state observations.

Seeing as we are replacing the throwing overload of copy_file() in the Filesystem TS with a result returning edition instead, it would make sense if an attempt to observe the value of an unsuccessful fs_result threw the exact same filesystem_error as the Filesystem TS does.

Telling Outcome how to throw a filesystem_error with payload of the failing paths is easy:


namespace filesystem2
{
  // If we would like Outcome to do something other than the default action (see next
  // section), we can declare this ADL discovered free function to customise what
  // to do instead.
  //
  // Note that rvalue semantics are propagated internally by Outcome, so if the user
  // called .value() on a rvalued result, failure_info will be moved rather than
  // copied from the result. That means we can overload once with value semantics,
  // and not need to overload for lvalue and rvalue situations unless we really feel
  // we need to for some reason.
  inline void throw_as_system_error_with_payload(failure_info fi)
  {
    // If the error code is not filesystem related e.g. ENOMEM, throw that as a standard STL exception.
    outcome::try_throw_std_exception_from_error(fi.ec);
    // Throw the exact same filesystem_error exception which the throwing copy_file() edition does.
    throw filesystem_error(fi.ec.message(), std::move(fi.path1), std::move(fi.path2), fi.ec);
  }
}
View this code on Github

Usage of our new “upgraded” Filesystem copy_file() might now be as follows:


  // Non-throwing use case
  auto o = filesystem2::copy_file("dontexist", "alsodontexist");
  if(!o)
  {
    std::cerr << "Copy file failed with error " << o.error().ec.message()                   //
              << " (path1 = " << o.error().path1 << ", path2 = " << o.error().path2 << ")"  //
              << std::endl;
  }

  // Throwing use case
  try
  {
    // Try to observe the successful value, thus triggering default actions which invokes
    // our throw_as_system_error_with_payload() above which then throws filesystem_error
    // exactly like the Filesystem TS does for its throwing overload.
    filesystem2::copy_file("dontexist", "alsodontexist").value();
  }
  catch(const filesystem2::filesystem_error &e)
  {
    std::cerr << "Copy file failed with exception " << e.what()                 //
              << " (path1 = " << e.path1() << ", path2 = " << e.path2() << ")"  //
              << std::endl;
  }
  catch(const std::exception &e)
  {
    std::cerr << "Copy file failed with exception " << e.what()  //
              << std::endl;
  }
  
View this code on Github