std::expected 值类型不同时如何向上传播错误?
数据小陈
结论
C++23 的 std::expected::and_then 可以自动传播错误。链上各步的值类型可以不同,但错误类型必须相同。使用时需要把依赖前一步结果的操作写在回调中;标准库没有类似 TRY 的语法来省去所有控制流样板。
用 and_then 链接解析步骤
题目中的 ParseBar() 返回 std::expected<Bar, Error>,ParseFoo() 则返回 std::expected<Foo, Error>。可以用 and_then 串联两个解析步骤:
auto ParseFoo() -> std::expected<Foo, Error> {
return ParseBar().and_then([](Bar bar0) {
return ParseBar().and_then(
[bar0 = std::move(bar0)](Bar bar1) mutable
-> std::expected<Foo, Error> {
Foo foo;
foo.pair[0] = std::move(bar0);
foo.pair[1] = std::move(bar1);
return foo;
});
});
}
如果第一次调用 ParseBar() 返回错误,外层 and_then 会把错误传到最终的 std::expected<Foo, Error>。如果第一次调用成功、第二次失败,内层 and_then 会传递第二个错误。两次调用都成功后,回调会构造并返回 Foo。
这里外层和内层 expected 的值类型都是 Bar,最终返回值的值类型则是 Foo。这没有问题:and_then 的回调可以返回值类型不同的 std::expected,只要错误类型相同即可。
为什么仍可能选择显式检查
and_then 会自动传递错误,但依赖前一步结果的操作必须放在回调里。步骤较多、需要共享局部状态或逻辑分支较多时,嵌套回调可能不如显式检查清楚。此时可以直接写:
if (!bar0) {
return std::unexpected{std::move(bar0.error())};
}
如果 ParseFoo() 的返回类型是 std::expected<Foo, Error>,就可以用 std::unexpected<Error> 返回错误。显式检查也便于在传递错误前记录上下文或转换错误。
注意事项
and_then是 C++23std::expected提供的 monadic 操作。使用前需确认编译器和标准库实现支持相应功能。and_then回调返回的std::expected必须使用相同的错误类型。如果各步骤的错误类型不同,需要显式转换错误类型或调整接口。and_then适合串联依赖前一步成功结果的操作。若只需把成功值转换成另一个值,可以考虑transform。- 是否采用 monadic 写法,取决于控制流的复杂度。它可以省去显式的错误分支,但不一定让整个函数更短或更易读。
备注:内容仅供参考。
