如何编写可通过函数参数自动推导类型的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
相关产品推荐
相关产品推荐

