Phase 2 construction

Its phase 2 constructor:

// Phase 2 static member constructor function, which cannot throw
inline outcome::result<file_handle> file_handle::file(file_handle::path_type path, file_handle::mode mode) noexcept
{
  // Perform phase 1 of object construction
  file_handle ret;

  // Perform phase 2 of object construction
  int flags = 0;
  switch(mode)
  {
  case mode::attr_read:
  case mode::read:
    flags = O_RDONLY;
    break;
  case mode::attr_write:
  case mode::write:
    flags = O_RDWR;
    break;
  case mode::append:
    flags = O_APPEND;
    break;
  default:
    return std::errc::invalid_argument;
  }
  ret._fd = ::open(path.u8string().c_str(), flags);
  if(-1 == ret._fd)
  {
    // Note that if we bail out here, ~file_handle() will correctly not call ::close()
    return {errno, std::system_category()};
  }
  if(-1 == ::fstat(ret._fd, &ret._stat))
  {
    // Note that if we bail out here, ~file_handle() will correctly call ::close()
    return {errno, std::system_category()};
  }

  // Returning ret directly is an area full of compiler specific behaviour quirks,
  // so be explicit by wrapping into an initialiser list with embedded move.
  return {std::move(ret)};
}
View this code on Github

The static member function implementing phase 2 firstly calls phase 1 which puts the object into a legally destructible state. We then proceed to implement phase 2 of construction, filling in the various parts as we go, reporting via result any failures.

Remember that operator new has a non-throwing form, new(std::nothrow).

For the final return, in theory we could just return ret and depending on the C++ version currently in force, it might work via move, or via copy, or it might refuse to compile. You can of course type lots of boilerplate to be explicit, but this use via initialiser list is a reasonable balance of explicitness versus brevity, and it should generate minimum overhead code irrespective of compiler, C++ version, or any other factor.