如何在 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& 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<std::string>
而你想要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& name
替换为name
)。那么像int val = 0; if_let(val, get_maybe_int()) ;
或if_let(arr[0], get_maybe_val());
这样的东西是可能的,也可以保持const
- 正确。以上是关于如何在 if 语句中最好地测试和解包 std::optional的主要内容,如果未能解决你的问题,请参考以下文章
如何使我的多个 if 语句起作用?或者最好如何在我的代码中使用嵌套循环