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

如何编写可通过函数参数自动推导类型的C++20模板函数

实现方案

核心问题原因

原来的实现使用std::function作为入参,模板参数推导阶段不会处理用户自定义的隐式转换(lambda到std::function的转换属于这类),因此无法自动推导TElement类型。

C++20 实现思路

完全放弃std::function作为入参,直接用通用模板接受任意可调用对象,结合C++20概念做约束,通过类型萃取从可调用对象的签名中自动提取元素类型,同时支持额外入参、任意参数位置、任意返回值的适配。

完整实现代码

#include <vector>
#include <functional>
#include <type_traits>
#include <utility>
#include <tuple>

// 类型萃取:从可调用对象签名中提取元素类型
template<typename F, size_t ElemArgIdx = 1>
struct CallableTraits {
private:
    // 处理const成员函数(含无状态/有状态lambda)
    template<typename R, typename C, typename... Args>
    static constexpr auto get_element_type(R(C::*)(Args...) const) -> std::remove_pointer_t<std::tuple_element_t<ElemArgIdx, std::tuple<Args...>>>;
    // 处理非const成员函数
    template<typename R, typename C, typename... Args>
    static constexpr auto get_element_type(R(C::*)(Args...)) -> std::remove_pointer_t<std::tuple_element_t<ElemArgIdx, std::tuple<Args...>>>;
    // 处理普通函数指针
    template<typename R, typename... Args>
    static constexpr auto get_element_type(R(*)(Args...)) -> std::remove_pointer_t<std::tuple_element_t<ElemArgIdx, std::tuple<Args...>>>;
public:
    using ElementType = decltype(get_element_type(std::declval<F>()));
};

// 概念约束:校验可调用对象符合调用要求
template<size_t CountArgIdx, size_t ElemArgIdx, typename F, typename... ExtraArgs>
concept ArrayLoadable = requires(F&& func, uint32_t* count, typename CallableTraits<std::remove_cvref_t<F>, ElemArgIdx>::ElementType* elem, ExtraArgs&&... args) {
    std::invoke(std::forward<F>(func), std::forward<ExtraArgs>(args)..., count, elem);
};

// 通用LoadArray实现
template<size_t CountArgIdx = 0, size_t ElemArgIdx = 1, typename F, typename... ExtraArgs>
requires ArrayLoadable<CountArgIdx, ElemArgIdx, F, ExtraArgs...>
auto LoadArray(F&& func, ExtraArgs&&... extra_args) {
    using TElement = typename CallableTraits<std::remove_cvref_t<F>, ElemArgIdx>::ElementType;
    uint32_t count;

    // 第一次调用:获取元素总数
    [&]<std::size_t... I>(std::index_sequence<I...>) {
        auto arg_tuple = std::forward_as_tuple(std::forward<ExtraArgs>(extra_args)...);
        std::invoke(std::forward<F>(func),
            [&]<std::size_t N>() {
                if constexpr (N == CountArgIdx) return &count;
                else if constexpr (N == ElemArgIdx) return static_cast<TElement*>(nullptr);
                else return std::get<N < CountArgIdx ? N : N-1>(arg_tuple);
            }.template operator()<I>()...
        );
    }(std::make_index_sequence<sizeof...(ExtraArgs) + 2>{});

    // 第二次调用:填充元素到vector
    std::vector<TElement> elements(count);
    [&]<std::size_t... I>(std::index_sequence<I...>) {
        auto arg_tuple = std::forward_as_tuple(std::forward<ExtraArgs>(extra_args)...);
        std::invoke(std::forward<F>(func),
            [&]<std::size_t N>() {
                if constexpr (N == CountArgIdx) return &count;
                else if constexpr (N == ElemArgIdx) return elements.data();
                else return std::get<N < CountArgIdx ? N : N-1>(arg_tuple);
            }.template operator()<I>()...
        );
    }(std::make_index_sequence<sizeof...(ExtraArgs) + 2>{});

    return elements;
}

使用示例

基础用法(计数/元素参数在前两位)

无需手动指定元素类型,自动推导:

std::vector<Cat> cats = LoadArray([](uint32_t* const count, Cat* const cats) {
  LoadCats(count, cats);
});

带额外前置参数的用法

比如目标函数签名为void LoadDogs(int owner_id, uint32_t* count, Dog* dogs),计数参数在索引1,元素参数在索引2:

// 指定计数和元素参数的索引位置,传入额外参数owner_id=1001
std::vector<Dog> dogs = LoadArray<1, 2>([](int owner_id, uint32_t* count, Dog* dogs) {
    LoadDogs(owner_id, count, dogs);
}, 1001);

带返回值的函数适配

无需特殊处理,返回值会被自动忽略:

std::vector<Food> foods = LoadArray([](uint32_t* count, Food* food) -> bool {
    return LoadFoods(count, food);
});

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.03 23:39:04