C++14如何实现编译期固定数量同类型参数的模板函数
问题描述
我尝试创建一个模板形式的函数,使其可接收多个同类型参数,且参数数量在编译期即可确定,相关代码如下:
struct Foo { int a, b, c; }; template <uint32_t argsCount, typename T> void fun(T ...args) // 要求参数最大数量等于argsCount { // ... // std::array<T, argsCount>{ args... }; } int main() { fun<3, Foo>( { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } ); // 不希望使用以下写法: // fun( Foo{}, Foo{}, Foo{} ); // 也不希望: // fun<Foo, Foo, Foo>( ... ); return 0; }
实现该函数需要满足以下约束条件:
- 禁止使用堆内存分配
- 禁止使用
va_args
请问C14中是否可以实现类似功能(优先提供C14的实现方案,也需要了解更新版本C++中的对应解决方案)?
编辑说明:已清理初始版本中粗糙的伪代码。
实现方案
首先说明原写法无法通过编译的核心原因:
- C++中可变参数包的正确声明语法是
typename... Args,T...args的写法无法让所有参数保持同一类型T - 花括号初始化列表
{...}属于非推导语境,直接作为模板函数的T类型参数传入时,编译器无法自动推导T的类型,必须通过匹配固定签名的可调用对象触发隐式类型转换。
C++14 实现
通过模板元编程生成固定参数数量的函数签名,触发花括号列表到T类型的隐式转换,全程在栈上完成操作,无堆分配、无C风格可变参数:
#include <array> #include <cstdint> #include <type_traits> struct Foo { int a, b, c; }; namespace detail { // 实际业务逻辑实现 template <typename T, uint32_t N> struct fun_impl { template <typename... Args> static void run(Args... args) { static_assert(sizeof...(Args) == N, "传入参数数量与argsCount不匹配"); std::array<T, N> arr{args...}; // 在此处编写具体业务逻辑,arr中已经按顺序存储了所有传入参数 } }; // 递归生成N个T参数的函数指针类型 template <typename T, uint32_t N> struct sig_matcher { using type = typename sig_matcher<T, N-1>::type(*)(T); }; template <typename T> struct sig_matcher<T, 0> { using type = void(*)(); }; } // 对外接口,返回对应签名的函数指针 template <uint32_t argsCount, typename T> typename detail::sig_matcher<T, argsCount>::type fun() { return &detail::fun_impl<T, argsCount>::run; } int main() { // 调用写法与预期完全一致,无需修改 fun<3, Foo>( { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } ); // 传参数量错误、参数无法转换为T时会直接编译报错 return 0; }
该方案的核心原理是:fun<3, Foo>()会返回一个类型为void(*)(Foo, Foo, Foo)的函数指针,后续传入的三个花括号列表会被编译器隐式构造为Foo类型,自动完成类型转换和参数数量校验。所有参数都直接存储在栈上的std::array中,无任何堆分配操作。
C++17 实现
C++17支持折叠表达式、std::is_constructible_v特性,同时可以用泛型lambda作为包装,简化实现逻辑:
#include <array> #include <cstdint> #include <type_traits> #include <utility> struct Foo { int a, b, c; }; namespace detail { template <uint32_t N, typename T, typename... Args> void fun_impl(Args&&... args) { static_assert(sizeof...(Args) == N, "传入参数数量与argsCount不匹配"); static_assert((std::is_constructible_v<T, Args&&> && ...), "参数无法构造为指定类型T"); std::array<T, N> arr{ T{std::forward<Args>(args)}... }; // 业务逻辑 } } // 用变量模板包装泛型lambda,支持花括号列表隐式转换 template <uint32_t argsCount, typename T> constexpr auto fun = [](auto&&... args) { return detail::fun_impl<argsCount, T>(std::forward<decltype(args)>(args)...); }; int main() { fun<3, Foo>( { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } ); return 0; }
C++20 简化实现
C++20引入concept特性,可以更直观地约束参数类型和数量,去掉冗余的static_assert:
#include <array> #include <cstdint> #include <concepts> #include <utility> struct Foo { int a, b, c; }; template <uint32_t argsCount, typename T> constexpr auto fun = []<std::constructible_from<T>... Args>(Args&&... args) requires (sizeof...(Args) == argsCount) { std::array<T, argsCount> arr{ T{std::forward<Args>(args)}... }; // 业务逻辑 }; int main() { fun<3, Foo>( { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } ); return 0; }
所有版本的实现都满足约束:无堆内存分配、不使用va_args,参数数量和类型校验全部在编译期完成,调用形式完全符合需求。
内容的提问来源于stack exchange,提问作者wjan
相关产品推荐
相关产品推荐

