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

基于C++模板根据可调用对象返回值实现DoInvoke分支逻辑

实现可调用对象返回值类型的自动适配

要让MakeEvent根据传入可调用对象的返回值类型,自动让DoInvoke执行对应逻辑,我们可以用C++17的if constexpr做编译期分支判断,配合自定义类型特征来识别std::future类型。下面是完整的可运行代码:

#include <future>
#include <functional>
#include <type_traits>

// 自定义类型特征:判断一个类型是不是std::future的特化
template <typename T>
struct is_future : std::false_type {};

template <typename T>
struct is_future<std::future<T>> : std::true_type {};

// 便捷常量,简化类型判断
template <typename T>
constexpr bool is_future_v = is_future<T>::value;

class EventImpl {
public:
    virtual ~EventImpl() = default;
    std::future<void> Invoke() { return DoInvoke(); }
private:
    virtual std::future<void> DoInvoke() = 0;
};

template <typename... Ts>
EventImpl* MakeEvent(Ts&&... args) {
    // 推导bind后可调用对象的返回类型
    using BoundFunc = decltype(std::bind(std::forward<Ts>(args)...));
    using ResultType = std::invoke_result_t<BoundFunc>;

    class EventMemberImpl : public EventImpl {
    public:
        EventMemberImpl(Ts&&... args)
            : m_function(std::bind(std::forward<Ts>(args)...)) {}

    protected:
        virtual ~EventMemberImpl() {}

    private:
        std::future<void> DoInvoke() override {
            if constexpr (is_future_v<ResultType>) {
                // 如果可调用对象返回std::future,直接调用并转换为std::future<void>
                auto task_future = m_function();
                // 包装一下,确保原future的任务完成后才返回
                return std::async(std::launch::deferred, [fut = std::move(task_future)]() mutable {
                    fut.get();
                });
            } else {
                // 如果返回普通类型或void,执行后返回空的std::future<void>
                m_function();
                return std::future<void>();
            }
        }

        // 用推导出来的返回类型定义std::function,适配所有情况
        std::function<ResultType()> m_function;
    };

    return new EventMemberImpl(std::forward<Ts>(args)...);
}

// 测试用例
int foo() { return 5; }
std::future<int> baz() {
    return std::async(std::launch::async, []() { return 42; });
}
void bar() {}

int main() {
    EventImpl* event1 = MakeEvent(&foo);
    EventImpl* event2 = MakeEvent(&baz);
    EventImpl* event3 = MakeEvent(&bar);

    // 执行所有事件并等待完成
    event1->Invoke().get();
    event2->Invoke().get();
    event3->Invoke().get();

    // 记得释放内存
    delete event1;
    delete event2;
    delete event3;
    return 0;
}

核心逻辑说明

  1. 类型特征is_future:
    我们写了一个简单的模板结构体,用来识别某个类型是不是std::future的特化版本。通过部分特化,当传入的类型是std::future<T>时,这个特征的value会是true,其他情况则为false。is_future_v是个便捷常量,让我们在代码里不用写::value,更简洁。

  2. 返回类型推导:
    在MakeEvent里,我们先推导std::bind之后得到的可调用对象类型,再用std::invoke_result_t获取它的返回类型ResultType——这一步是为了后续判断可调用对象到底返回什么。

  3. 编译期分支判断:
    用if constexpr(C++17开始支持)在编译时就确定走哪个分支:

    • 如果返回类型是std::future,就调用可调用对象得到future,然后把它包装成std::future<void>(这样才能符合DoInvoke的返回类型要求),同时确保原future的任务会被等待完成。
    • 如果是普通类型(比如int)或者void,就直接执行可调用对象,然后返回一个空的std::future<void>。
  4. 通用的std::function定义:
    我们用推导出来的ResultType定义m_function的类型,这样不管可调用对象返回什么,std::function都能正确适配,不用写两个不同的成员变量。

兼容旧版本C++的提示

如果需要支持C11或C14(没有if constexpr),可以用SFINAE和模板特化来实现两个不同的EventMemberImpl版本,分别处理返回future和非future的情况。不过C++17及以上用if constexpr会更简洁直观。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.15 08:31:50