如何在 if 语句中最好地测试和解包 std::optional

Posted

技术标签:

【中文标题】如何在 if 语句中最好地测试和解包 std::optional【英文标题】:How best to test and unwrap std::optional in an if statement 【发布时间】:2020-06-29 22:24:01 【问题描述】:

我有多个返回 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 语句。

【问题讨论】:

您发布的obvious simple solution 不是很好吗?它仍然简洁,不引入宏,并明确说明您想要什么,从可维护性的角度来看可能会更好。 @AdamKotwasinski 这很好,我同意,但如果您有许多选项要解包并且想要简化与 * 解包相关的代码,这不是最好的 @Alex: "例如,显而易见的简单解决方案" 那不是复制对象吗?如果MyType 具有一定的规模/复杂性,在性能方面使用*result 会不会更好? @NicolBolas 是的。正如@Barry 所写,更好的选择是auto&amp; result = *resultOpt; 【参考方案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;
    

【讨论】:

隐式转换并不能满足所有情况。例如。如果auto x = blub() 返回opt&lt;std::string&gt; 而你想要x.size()【参考方案2】:

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

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

第二个最好的方法是给可选的名称取一个丑陋的名称并为其命名一个更好的别名:

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

我认为这已经足够了。这是有意隐藏外部范围名称(即同时命名optional 和内部别名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

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

【讨论】:

这个宏有点搞笑。 if false else 很有趣。好的,我将使用*result 选项。但是出于好奇,除了可能的名称冲突之外,您的宏还有什么own problems @Barry 也许可以让用户提供完整的名称声明(因此在宏中将auto&amp; name 替换为name)。那么像int val = 0; if_let(val, get_maybe_int()) ;if_let(arr[0], get_maybe_val()); 这样的东西是可能的,也可以保持const- 正确。

以上是关于如何在 if 语句中最好地测试和解包 std::optional的主要内容,如果未能解决你的问题,请参考以下文章

如何使我的多个 if 语句起作用?或者最好如何在我的代码中使用嵌套循环

如何从服务中可靠地捕获 Windows 登录、注销、锁定和解锁事件?

java中Socket如何实现数据包传输的打包和解包?

Delphi中if语句的评估顺序

如何在 C++ 中最好地实现 DPLL?

如何在 Flutter 集成测试中最好地存根/模拟 rest API 调用