How to efficiently return an std::optional
Asked Answered
M

1

13

I would like to ask how to return an std::optional in an efficient way and I would like to use std::make_optional(). For example lets have this code snippet:

std::optional<Path> CreateCanonicalPath(const std::string_view& path)
{
  std::error_code errorCode;
  const auto result = std::filesystem::weakly_canonical(std::filesystem::u8path(path), errorCode);
  return !errorCode ? std::make_optional(result) : std::nullopt;
}

I am particularly interested whether there is any optimization in passing result to std::make_optional. Would it be better to use std::make_optional(std::move(result))?

And does it prevent any RVO or NVRO? The result is a local variable but it is not exactly in a return statement so I assume the compiler can't use move by itself.

Marquismarquisate answered 20/1, 2023 at 9:18 Comment(8)
Something related: youtu.be/dGCxMmGvocE?t=430Hock
Did you create some measurements (benchmark tests)? Why do you think this is code which needs to be optimized? Is this a wild guess, or did you profiled your production code to spot bottle necks? From all of this weakly_canonical will be slowest part since it interacts with OS.Sarita
What is the point of wrapping std::path with std::optional? What is the difference between a returned empty optional and empty path in case of errors? AFAIK, optional is used mainly for types that themselves don't provide an "empty" state (such as integers), or where the empty state may be a valid result (such as with std::string).Featherweight
@DanielLangr: An empty path is not necessary an invalid path. It is a valid result of path slicing, concatenating and normalizing, differentiating, etc. I don't think it necessary indicates an error. But I don't have much experience with std::filesystem.Indeterminacy
@JiříLechner Me neither, but what you write makes sense.Featherweight
Why do you want to use make_optional() ? It kind off gets in the way.Pageboy
@MichaëlRoy, make_optional() is a factory pattern, it may allow to use some optimizations. For example make_shared() does it and it is kind of significant. I don't think it is the case for make_optional() but I like to have it consistent with other make_...() functions.Indeterminacy
@JiříLechner It may allow some optimization, but not in this case, since it would add a std::optional move assignment, and thus defeat return-in-place optimization.Pageboy
H
15

There's one obvious improvement:

std::optional<Path> CreateCanonicalPath(const std::string_view& path)
{
  std::error_code errorCode;
  auto result = std::filesystem::weakly_canonical(std::filesystem::u8path(path), errorCode);
  return !errorCode ? std::make_optional(std::move(result)) : std::nullopt;
}

Making the transitory object const will require employing copy-construction as part of instantiating the returned std::optional. This tweak should result in employing move semantics.

After this point, any further improvements would dependent highly on the compiler behavior. It's unlikely, but it's possible that, if benchmarked, some minor performance variances can be observed with alternate syntaxes, such as, for example:

std::optional<Path> CreateCanonicalPath(const std::string_view& path)
{
  std::error_code errorCode;
  auto result = std::make_optional(std::filesystem::weakly_canonical(std::filesystem::u8path(path), errorCode));

  if (errorCode)
     result.reset();

  return result;
}

If it's determined that the compiler will choose to elide the copy, as allowed by NVRO, then it's worth benchmarking this, as well. But only actual benchmarking will produce useful results.

Hand answered 20/1, 2023 at 10:19 Comment(1)
So in the second snippet the move semantic will be used since the function returns local variable. And depending on the compiler even NVRO may be applied.Indeterminacy

© 2022 - 2024 — McMap. All rights reserved.