This is an archive of the discontinued LLVM Phabricator instance.

Support: Add Expected<T>::moveInto() to avoid extra names
ClosedPublic

Authored by dexonsmith on Oct 21 2021, 4:05 PM.

Details

Summary

Expected<T>::moveInto() takes as an out parameter any OtherT& that's
assignable from T&&. It moves any stored value before returning
takeError().

There are two existing patterns that moveInto() cleans up:

// If the variable is new:
Expected<std::unique_ptr<int>> ExpectedP = makePointer();
if (!ExpectedP)
  return ExpectedP.takeError();
std::unique_ptr<int> P = std::move(*ExpectedP);

// If the target variable already exists:
if (Expected<T> ExpectedP = makePointer())
  P = std::move(*ExpectedP);
else
  return ExpectedP.takeError();

moveInto() takes less typing and avoids needing to name (or leak into
the scope) an extra variable.

// If the variable is new:
std::unique_ptr<int> P;
if (Error E = makePointer().moveInto(P))
  return E;

// If the target variable already exists:
if (Error E = makePointer().moveInto(P))
  return E;

Another use case is writing unit tests, to log errors (but continue)
when there's an unexpected failure. E.g.:

// Crash on error, or undefined in non-asserts builds.
std::unique_ptr<MemoryBuffer> MB = cantFail(makeMemoryBuffer());

// Avoid crashing on error without moveInto() :(.
Expected<std::unique_ptr<MemoryBuffer>>
    ExpectedMB = makeMemoryBuffer();
ASSERT_THAT_ERROR(ExpectedMB.takeError(), Succeeded());
std::unique_ptr<MemoryBuffer> MB = std::move(ExpectedMB);

// Avoid crashing on error with moveInto() :).
std::unique_ptr<MemoryBuffer> MB;
ASSERT_THAT_ERROR(makeMemoryBuffer().moveInto(MB), Succeeded());

Diff Detail

Event Timeline

dexonsmith requested review of this revision.Oct 21 2021, 4:05 PM
dexonsmith created this revision.
Herald added a project: Restricted Project. · View Herald TranscriptOct 21 2021, 4:05 PM
dblaikie accepted this revision.Oct 21 2021, 5:26 PM

Sounds nice to have!

llvm/include/llvm/Support/Error.h
584

Should this be && qualified, so it can only be called on a temporary Expected, since it's going to consume it? (I mean takeError isn't rvalue ref qualified and get isn't - so it's not clear it has to be)

This revision is now accepted and ready to land.Oct 21 2021, 5:26 PM
dexonsmith added inline comments.Oct 21 2021, 5:46 PM
llvm/include/llvm/Support/Error.h
584

Should this be && qualified, so it can only be called on a temporary Expected, since it's going to consume it?

Maybe? I can't think of why you'd want to call this on a non-temporary. I don't see any problems with doing it though... and I suppose it'd guide people toward the pattern of not naming it?

Happy to do it; if you feel strongly, let me know; otherwise I'll stew for a bit. Leaning toward your suggestion, and then && can be removed later if it turns out there's a good reason to allow it on lvalues.

(I mean takeError isn't rvalue ref qualified and get isn't - so it's not clear it has to be)

I don't feel like && is quite right for these APIs though. You need to name the value if you're going to both check the error and extract the value. Any std::move()s after that would feel like extra boilerplate.

dblaikie added inline comments.Oct 21 2021, 6:10 PM
llvm/include/llvm/Support/Error.h
584

Fair ponit re: the other operations clearly don't consume the /whole/ object, you have to use both operations. Whereas this new API does consume the whole object, there's nothing else you can do with it after using this operation, I think? (since the value and the error have been moved-from)

So, yeah, at that point I do lean in favor of &&. Not strongly, but yeah, probably enough to encourage it, give it a go, we can remove it later as you say.

This revision was landed with ongoing or failed builds.Oct 22 2021, 11:47 AM
This revision was automatically updated to reflect the committed changes.