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

如何推导可调用对象的参数?ECS风格数据注册表的Lambda适配疑问

适配Lambda表达式的ECS注册表实现方案

你的ECS注册表当前仅支持函数指针调用,无法适配Lambda表达式的核心原因是:Lambda是闭包类型,而非原生函数指针,无法直接通过R (*f)(Args...)推导参数类型。我们可以通过提取可调用对象的调用签名来解决这个问题,以下是具体实现方案:

1. 实现可调用对象的签名提取模板

首先定义function_traits模板,用于提取任意可调用对象(函数指针、Lambda、函数对象)的返回值和参数类型:

#include <tuple>
#include <utility>
#include <type_traits>

// 基础模板
template<typename F>
struct function_traits;

// 特化:普通函数指针
template<typename R, typename... Args>
struct function_traits<R (*)(Args...)> {
    using return_type = R;
    using args_tuple = std::tuple<std::remove_reference_t<Args>...>;
    static constexpr size_t args_count = sizeof...(Args);
};

// 特化:Lambda/函数对象的const operator()(默认Lambda是const的)
template<typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...) const> {
    using return_type = R;
    using args_tuple = std::tuple<std::remove_reference_t<Args>...>;
    static constexpr size_t args_count = sizeof...(Args);
};

// 特化:mutable Lambda的非const operator()
template<typename R, typename C, typename... Args>
struct function_traits<R (C::*)(Args...)> {
    using return_type = R;
    using args_tuple = std::tuple<std::remove_reference_t<Args>...>;
    static constexpr size_t args_count = sizeof...(Args);
};

// 偏特化:自动推导可调用对象的operator()
template<typename F>
struct function_traits : public function_traits<decltype(&F::operator())> {};

2. 修改Reg结构体的call方法

基于上面的签名提取模板,重构Reg的call方法,使其支持任意可调用对象:

template <typename... DataType>
struct Reg {
    std::tuple<DataType...> data;

    // 对外暴露的call接口,适配所有可调用对象
    template<typename F>
    auto call(F&& f) -> typename function_traits<std::decay_t<F>>::return_type {
        return call_impl(
            std::forward<F>(f),
            std::make_index_sequence<function_traits<std::decay_t<F>>::args_count>{}
        );
    }

private:
    // 内部实现:用索引序列展开参数
    template<typename F, size_t... Idx>
    auto call_impl(F&& f, std::index_sequence<Idx...>) -> typename function_traits<std::decay_t<F>>::return_type {
        using ArgsTuple = typename function_traits<std::decay_t<F>>::args_tuple;
        // 从注册表的tuple中取出对应类型的参数,传递给可调用对象
        return f(std::get<std::tuple_element_t<Idx, ArgsTuple>>(data)...);
    }

    // 原有add_data方法保持不变
    template<typename T>
    constexpr Reg<T, DataType...> add_data(T new_data = {}) && {
        return {std::tuple_cat(std::move(data), std::tuple<T>(new_data))};
    }
};

3. 测试验证

以下是包含Lambda调用的测试代码:

#include <iostream>

// 测试系统函数
void system_with_double(double& d) { d += 1.0; }
void system_with_float(float& f) { f += 2.0f; }
void system_with_double_and_float(double& d, float& f) { d += 3.0; f += 4.0f; }

int main() {
    auto registry = Reg<>{}
        .add_data<int>(42)
        .add_data<double>(3.14)
        .add_data<float>(1.5f);

    // 调用普通函数指针
    registry.call(system_with_double);
    registry.call(system_with_float);
    registry.call(system_with_double_and_float);

    // 调用普通Lambda
    registry.call([](int& i, double& d) {
        i *= 2;
        d *= 2;
    });

    // 调用mutable Lambda(若需要修改捕获变量)
    int capture_val = 10;
    registry.call([capture_val](float& f) mutable {
        f += capture_val;
        capture_val += 5; // mutable允许修改捕获变量
    });

    // 输出验证结果
    std::cout << "int: " << std::get<int>(registry.data) << std::endl;    // 84
    std::cout << "double: " << std::get<double>(registry.data) << std::endl; // 8.28
    std::cout << "float: " << std::get<float>(registry.data) << std::endl;  // 15.0
}

关键说明

  • 该实现通过function_traits自动解析可调用对象的参数类型,无需手动指定,完全适配Lambda(包括mutable类型)、函数指针和自定义函数对象。
  • 如果注册表中缺少可调用对象需要的参数类型,std::get会触发编译错误,这符合ECS的设计原则——系统只能访问注册表中已注册的数据。
  • 保持了原有add_data方法的功能和调用方式,无需修改现有代码逻辑。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.23 01:02:48