8

我有多个返回std::optional<T>. 这是一个虚构类型的示例MyType

struct MyType {
    // ...
}

std::optional<MyType> calculateOptional() {
    // ... lengthy calculation

    if (success) {
        return MyType(/* etc */);
    }

    return std::nullopt;
}

让我们假设这些函数的运行成本很高,我想避免多次调用它们。

当调用它们时,我想立即测试可选项,如果它确实包含一个值,我想立即使用它并且不再使用它。例如,在 Swift 中,我可以使用标准if-let语句:

if let result = calculateOptional() {
    // Use result var
}

我想在 C++ 中复制这种测试和解包行为,同时在使用时保持代码尽可能干净。例如,显而易见的简单解决方案(至少对我而言)是:

if (auto result = calculateOptional()) {
    MyType result_unwrapped = *result;
    // Use result_unwrapped var
}

但是您必须在 内展开if,或*result在任何地方使用,这与 Swift 无关。

到目前为止,我唯一真正接近 Swift 外观和感觉的解决方案是:

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
}

...但我也不喜欢使用宏来替换普通if语句。

4

5 回答 5

8

就个人而言,我会这样做:

if (auto result = calculateOptional()) {
    // use *result
}

第二个最好的方法是给可选的一个丑陋的名字并为它创建一个更好的别名:

if (auto resultOpt = calculateOptional()) {
    auto& result = *resultOpt;
    // use result
}

我认为这已经足够好了。这是故意隐藏外部范围名称(即同时命名 theoptional和内部别名result)的一个很好的用例,但我认为我们不需要在这里发疯。即使使用*result也不是什么大问题——类型系统可能会捕获所有的误用。


如果我们真的想使用 Swift,那么您使用的宏需要默认构造——这并不是真正必要的。我们可以做得更好一点(理想情况下__opt被一种选择唯一名称的机制所取代,例如与 连接__LINE__):

#define if_let(name, expr)              \
    if (auto __opt = expr)              \
        if (auto& name = *__opt; false) {} else

如:

if_let(result, calculateOptional()) {
    // use result
} else {
    // we didn't get a result
}

这没有任何额外的开销或要求。但这有点荒谬,有其自身的问题,而且似乎不值得。但如果我们只是玩得开心,这是可行的。

于 2020-03-18T19:51:11.290 回答
0

另一个简单且可能更安全的方法:

#define unwrap(x, val, block) if (auto opt_##x = val) { auto &x = opt_##x; block }

用法:

unwrap(result, calculateOptional(), {
  // use result
});
于 2022-01-04T07:07:56.467 回答
-1

您可以将可选项包装在自己的类型中,隐式转换为类型并显式转换为bool. 抱歉,到目前为止我还没有测试过,但我认为它应该可以工作。

template<class T>
struct opt {
    std::optional<T> _optional; // public so it stays an aggregate, probably writing constructors is better

    explicit bool() const {
        return _optional.has_value();
    }

    T&() {
        return *_optional;
    }

    const T&() const {
         return *_optional;
    }

    T&&() && { // Let's be fancy
         return std::move(*optional);
    }
}

opt<int> blub(bool val) {
    return val ? opt<int>{0} : opt<int>{std::nullopt};
}

int main() {
    if(auto x = blub(val)) { // I hope this works as I think it does
        int y = x+1;
    }
}
于 2020-03-18T19:47:27.447 回答
-1

如果calculateOptional()返回一个std::pair<bool sucess, T result>或可以转换为一个,您可以使用以下构造:

if (auto [s, result] = calculatePair(); s) {

} else {

}

或者你使用例外;(...)捕获所有异常

try {
    auto result = calculate();

} catch (...) {

}

但你可以更具体

try {
    auto result = calculate();

} catch (nosuccess) {

}
于 2022-01-03T14:31:59.633 回答
-2

这可能是一种干净的方式,受到这篇文章中所有其他答案的启发:

template <typename T>
inline std::pair<bool, T> _unwrap(const std::optional<T> &val) {
    return { val.has_value(), *val };
}

#define unwrap(x, val) const auto &[_##x, x] = _unwrap(val); (_##x)

用法:

if (unwrap(result, calculateOptional())) {
  // result is now equivalent to *calculateOptional()
}

优点:

  • 你不要乱用 if 语句
  • 它保持一种类似方法的感觉
  • 您仍然可以在 if 语句的右侧添加更多条件

缺点:

  • 只读但又是可选的

很高兴听到你们可能认为此解决方案可能存在的任何问题/修复。

于 2022-01-03T10:49:07.537 回答