I have multiple functions that return a std::optional<T>
. Here's an example for a made-up type MyType
:
struct MyType {
// ...
}
std::optional<MyType> calculateOptional() {
// ... lengthy calculation
if (success) {
return MyType(/* etc */);
}
return std::nullopt;
}
Let's assume these functions are costly to run and I want to avoid calling them more than once.
When calling them I want to immediately test the optional, and if it does contain a value, I want to use it immediately and never again. In Swift, for example, I can use the standard if-let
statement:
if let result = calculateOptional() {
// Use result var
}
I would like to replicate this test-and-unwrap behavior in C++, while keeping the code as clean as possible at the point of use. For example, the obvious simple solution (to me at least) would be:
if (auto result = calculateOptional()) {
MyType result_unwrapped = *result;
// Use result_unwrapped var
}
But you have to unwrap inside the if
, or use *result
everywhere, which you don't have to do with Swift.
My only solution so far that genuinely gets close to the look and feel of Swift is:
template<typename T> bool optionalTestUnwrap(std::optional<T> opt, T& value) {
if (!opt.has_value()) { return false; }
value = *opt;
return true;
}
#define ifopt(var, opt) if (typename decltype((opt))::value_type (var); optionalTestUnwrap((opt), (var)))
ifopt (result, calculateOptional()) {
// Use result var
}
...but I'm also not a big fan of the use of a macro to replace a normal if
statement.
Personally, I would just do:
if (auto result = calculateOptional()) {
// use *result
}
with a second best of giving the optional an ugly name and making a nicer-named alias for it:
if (auto resultOpt = calculateOptional()) {
auto& result = *resultOpt;
// use result
}
I think this is good enough. It's a great use-case for intentionally shadowing an outer-scope name (i.e. naming both the optional
and the inner alias result
), but I don't think we need to go crazy here. Even using *result
isn't a big problem - the type system will likely catch all misuses.
If we really want to go in on Swift, the macro you're using requires default construction - and it's not really necessary. We can do a little bit better with (ideally __opt
is replaced by a mechanism that selects a unique name, e.g. concatenating with __LINE__
):
#define if_let(name, expr) \
if (auto __opt = expr) \
if (auto& name = *__opt; false) {} else
As in:
if_let(result, calculateOptional()) {
// use result
} else {
// we didn't get a result
}
This doesn't have any extra overhead or requirements. But it's kind of ridiculous, has its own problems, and doesn't seem worthwhile. But if we're just having fun, this works.