Search code examples
c++c++17stdoptional

How best to test and unwrap std::optional in an if statement


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.


Solution

  • 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.