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

如何写出可读性强的C++23单子回调组合代码?

提升C++23单子回调组合代码可读性的方法

你并没有用错单子模式,问题出在匿名Lambda的过度嵌套——这种写法会导致逻辑层级不断加深,形成"回调地狱"。C++23提供了多种特性和技巧,可以扁平化这种单子流程,让代码既保留单子的短路特性,又具备常规写法的可读性。

1. 提取嵌套Lambda为命名辅助函数

最直接的改进是把嵌套的匿名Lambda抽成有明确语义的命名函数/函数对象,让逻辑层级扁平化,同时每个函数的职责更清晰。

改进简单示例(foo/bar相乘)

std::optional<int> multiply_results(int foo_res, int bar_res) {
    return foo_res * bar_res;
}

std::optional<int> baz() {
    return foo().and_then([](int foo_res) {
        return bar().and_then(std::bind_front(multiply_results, foo_res));
    });
}

或者进一步拆分,完全消除内部Lambda:

std::optional<int> get_bar_and_multiply(int foo_res) {
    return bar().and_then([foo_res](int bar_res) {
        return foo_res * bar_res;
    });
}

std::optional<int> baz() {
    return foo().and_then(get_bar_and_multiply);
}

改进你的解析器示例

把内部嵌套的Lambda抽成命名函数,拆分后职责更明确:

template <Generator<TokenOrError> Gen>
std::optional<grammar::VariableExpression> Parser<Gen>::extract_variable(grammar::Expression expr) {
    return try_get<grammar::PrimaryExpression>(std::move(expr))
        .and_then([](grammar::PrimaryExpression primary_expr) {
            return try_get<grammar::VariableExpression>(std::move(primary_expr.data));
        });
}

template <Generator<TokenOrError> Gen>
typename Parser<Gen>::maybe_expression Parser<Gen>::parse_assignment_rhs(
    const token::Equal& equal, 
    const grammar::VariableExpression& variable
) {
    return expression()
        .map([variable](std::unique_ptr<grammar::Expression> assign_to) {
            return std::make_unique<grammar::Expression>(grammar::AssignmentExpression{
                variable, std::move(assign_to), variable.line_number
            });
        });
}

template <Generator<TokenOrError> Gen>
typename Parser<Gen>::maybe_expression Parser<Gen>::assignment() {
    return equality()
        .and_then([&](std::unique_ptr<grammar::Expression> expr) {
            return consume<token::Equal>()
                .transform([&](const token::Equal& equal) {
                    return extract_variable(std::move(*expr))
                        .transform([&](const grammar::VariableExpression& variable) {
                            return parse_assignment_rhs(equal, variable);
                        })
                        .value_or(tl::unexpected{Error{equal.line_number,
                            ErrorType::kBadAssign, 
                            fmt::format("Incomplete assignment expression") 
                        }});
                })
                .or_else([&] {
                    return std::move(expr);
                })
                .value();
        });
}

2. 利用C++23协程实现线性化流程

C20开始支持协程,C23完善了相关标准库支持。通过自定义支持std::optional/tl::expected的协程任务类型,可以把单子流程写成线性的同步风格代码,自动处理短路逻辑,完全消除嵌套。

自定义支持optional的协程任务

#include <coroutine>
#include <optional>

template <typename T>
struct OptionalTask {
    struct promise_type {
        std::optional<T> value;

        OptionalTask get_return_object() { return {std::coroutine_handle<promise_type>::from_promise(*this)}; }
        std::suspend_never initial_suspend() { return {}; }
        std::suspend_never final_suspend() noexcept { return {}; }
        void return_value(T val) { value = std::move(val); }
        void unhandled_exception() { std::terminate(); }

        // 支持co_await std::optional<T>,自动处理nullopt短路
        auto await_transform(std::optional<T> opt) {
            struct Awaiter {
                std::optional<T> opt;
                bool await_ready() { return opt.has_value(); }
                void await_suspend(std::coroutine_handle<>) {}
                T await_resume() { return std::move(opt).value(); }
            };
            return Awaiter{std::move(opt)};
        }
    };

    std::coroutine_handle<promise_type> handle;

    ~OptionalTask() { if (handle) handle.destroy(); }
    std::optional<T> get() {
        handle.resume();
        return std::move(handle.promise().value);
    }
};

用协程重写baz()

OptionalTask<int> baz() {
    int foo_res = co_await foo(); // 自动处理nullopt短路
    int bar_res = co_await bar();
    co_return foo_res * bar_res;
}

// 调用方式
std::optional<int> result = baz().get();

用协程重写你的解析器

协程可以把原本嵌套的回调完全改成线性逻辑(需为maybe_expression实现对应await_transform):

template <Generator<TokenOrError> Gen>
typename Parser<Gen>::maybe_expression Parser<Gen>::assignment() {
    auto expr = co_await equality();
    if (auto equal = consume<token::Equal>()) {
        auto variable = co_await extract_variable(std::move(*expr));
        auto assign_to = co_await expression();
        co_return std::make_unique<grammar::Expression>(grammar::AssignmentExpression{
            variable, std::move(assign_to), variable.line_number
        });
    } else {
        co_return std::move(expr);
    }
}

3. 自定义管道式Monadic组合子

通过重载operator|,可以把and_then/transform等操作改成管道风格,让代码按执行顺序从左到右流动,避免嵌套。

重载operator|用于std::optional

template <typename T, typename F>
auto operator|(std::optional<T> opt, F&& func) {
    return opt.and_then(std::forward<F>(func));
}

管道风格重写baz()

std::optional<int> baz() {
    return foo() 
        | [](int foo_res) { return bar() | [foo_res](int bar_res) { return foo_res * bar_res; }; };
}

配合std::bind_front还能进一步简化:

std::optional<int> multiply(int a, int b) { return a * b; }

std::optional<int> baz() {
    return foo() 
        | [](int f) { return bar() | std::bind_front(multiply, f); };
}

4. 使用C++23的std::bind_back简化参数绑定

C++23新增的std::bind_back可以更方便地绑定后序参数,配合命名函数减少Lambda的使用:

std::optional<int> multiply(int a, int b) { return a * b; }

std::optional<int> baz() {
    return foo().and_then(std::bind_back(multiply, bar().value()));
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.15 06:34:58