You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何以直观语法正确实现多个std::expected<void, E>的短路组合?

问题

我正在一个包含大量返回bool表示成功与否的函数(如orig_check1、orig_check2)的代码库中引入std::expected,这些底层函数常被同样返回bool的高层函数(如orig_check)调用。我希望为所有函数添加错误码,认为std::expected<void, std::string_view>是合适的选择。

以下程序的check函数中展示了三种组合底层函数check1和check2结果的方式:

#include <cassert>
#include <expected>
#include <string_view>
#include <iostream>
#include <functional>

bool orig_check1(int val) { return val % 2 == 1; }
bool orig_check2(int val, int threshold) { return val <= threshold; }
bool orig_check(int val) { return orig_check1(val) && orig_check2(val, 100); }

////////////////////

std::expected<void, std::string_view> check1(int val)
{
    if (val % 2 == 0) {
        return std::unexpected{"even"};
    }
    return {};
}

std::expected<void, std::string_view> check2(int val, int threshold)
{
    if (val > threshold) {
        return std::unexpected{"too big"};
    }
    return {};
}

template <typename E>
std::expected<void, E> operator&&(std::expected<void, E> lhs, std::expected<void, E> rhs)
{
    return !lhs ? lhs : rhs;
}

std::expected<void, std::string_view> check(int val)
{
    // 过于冗长
    auto ret1 = [&]() -> std::expected<void, std::string_view> {
        if (auto ret = check1(val); !ret) {
            return ret;
        }
        if (auto ret = check2(val, 100); !ret) {
            return ret;
        }
        return {};
    }();

    // 复杂度高
    std::expected ret2 = check1(val).and_then(std::bind_front(check2, val, 100));
    
    // 因缺少短路求值而不正确
    std::expected ret3 = check1(val) && check2(val, 100);
    
    // 验证功能等价性
    assert(ret1 == ret2 && ret1 == ret3);
    return ret1;
}

int main()
{
    check(0);
    check(1);
    check(123);
    return 0;
}

我对这些方案都不满意:方案1过于冗长,方案2复杂度高,方案3因缺少短路求值而不正确。理想状态是能使用方案3的语法且具备短路求值特性,请问这是否可行?若不可行,有其他可考虑的方案吗?

解决方案

一、实现支持短路求值的operator&&

你当前定义的operator&&会先计算左右两边的表达式再传入函数,自然没有短路效果。要实现短路,需要让右边的表达式延迟求值——可以通过接收可调用对象而非直接接收std::expected实例来实现:

template <typename E, typename F>
requires std::invocable<F> && std::same_as<std::invoke_result_t<F>, std::expected<void, E>>
std::expected<void, E> operator&&(std::expected<void, E>&& lhs, F rhs)
{
    if (!lhs) {
        return std::move(lhs);
    }
    return rhs();
}

这样调用时,右边的函数调用会被包装成lambda延迟执行,只有左边成功时才会触发:

std::expected<void, std::string_view> check(int val)
{
    return check1(val) && [&](){ return check2(val, 100); };
}

这个写法接近你想要的简洁性,同时保留了短路求值特性。

二、使用宏简化链式调用

如果觉得lambda还是有点繁琐,可以用宏来封装短路检查逻辑:

#define CHECK(expr) \
    do { \
        auto res = (expr); \
        if (!res) return res; \
    } while(false)

std::expected<void, std::string_view> check(int val)
{
    CHECK(check1(val));
    CHECK(check2(val, 100));
    return {};
}

宏的写法非常直观,和原始的bool检查逻辑风格最接近,几乎没有额外学习成本,适合大规模改造现有代码。

三、C++23的简化方案

C++23中std::expected的and_then可以直接配合lambda简化写法,虽然不如自定义运算符简洁,但无需额外重载:

std::expected<void, std::string_view> check(int val)
{
    return check1(val).and_then([&](){ return check2(val, 100); });
}

四、辅助函数实现链式短路检查

如果不想用宏或重载运算符,可以写一个通用的链式检查辅助函数,利用折叠表达式实现短路:

template <typename E, typename... Fs>
std::expected<void, E> chain_check(Fs&&... funcs)
{
    return (std::invoke(std::forward<Fs>(funcs)) && ...);
}

// 调用方式
std::expected<void, std::string_view> check(int val)
{
    return chain_check(
        [&](){ return check1(val); },
        [&](){ return check2(val, 100); }
    );
}

内容的提问来源于stack exchange,提问作者MarkB

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.18 09:43:11