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 outcome_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);
}
}
Reference documentation for the above functions:
- List of builtin
outcome_throw_as_system_error_with_payload()
overloads void try_throw_std_exception_from_error(std::error_code ec, const std::string &msg = std::string{})
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 outcome_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;
}