PAYATHON 2026

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++23 std::expected 提供的 monadic 操作。使用前需确认编译器和标准库实现支持相应功能。
  • and_then 回调返回的 std::expected 必须使用相同的错误类型。如果各步骤的错误类型不同,需要显式转换错误类型或调整接口。
  • and_then 适合串联依赖前一步成功结果的操作。若只需把成功值转换成另一个值,可以考虑 transform。
  • 是否采用 monadic 写法,取决于控制流的复杂度。它可以省去显式的错误分支,但不一定让整个函数更短或更易读。

备注:内容仅供参考。