C++17及更早版本下能否按约束用可变参数函数/模板传递可变栈参数?
结论
场景A(匹配目标函数可接收的参数数量)可以实现,场景B(由用户自定义整数指定次数)仅支持编译期常量的次数,运行时动态次数无法实现,因为C++是静态类型语言,函数参数的数量、类型都必须在编译期确定,无法根据运行时的数值动态生成参数列表。
以下实现完全满足你的三个约束:
- 不依赖任何STL、Boost或第三方库,仅用C++核心语法
- 无运行时循环、递归,参数展开完全在编译期完成
- 兼容C14及以上标准,自然满足C17要求
实现方案
首先你原有代码中使用float(...)类型的可变函数指针无法提取参数数量,我们需要基于目标函数的实际类型,通过编译期索引序列展开对应次数的pop_t调用。
1. 自定义编译期索引序列(无STL依赖)
我们自己实现编译期整数序列生成逻辑,不依赖STL的std::index_sequence:
template <size_t... Is> struct index_sequence {}; template <size_t N, size_t... Is> struct make_index_sequence_impl : make_index_sequence_impl<N-1, N-1, Is...> {}; template <size_t... Is> struct make_index_sequence_impl<0, Is...> { using type = index_sequence<Is...>; }; template <size_t N> using make_index_sequence = typename make_index_sequence_impl<N>::type;
2. 实现参数展开逻辑
通过模板提取函数参数个数,生成对应长度的索引序列,展开为N次pop_t调用:
// 你的pop_t实现,注意修正原有未定义行为 template<class U, class I> U pop_t(U* stack, I &stackidx, I &stacksize) { if(stacksize>0) { stacksize--; stackidx--; // 原写法stackidx = stackidx--属于未定义行为,已修正 return stack[stackidx]; } return stack[stackidx]; } // 核心展开逻辑:根据索引序列长度生成N次pop_t调用 template <class U, class I, class Func, size_t... Is> auto invoke_with_n_pops(Func func, U* stack, I& stackidx, I& stacksize, index_sequence<Is...>) { return func(pop_t(stack, stackidx, stacksize)...); } // 场景A实现:自动匹配目标函数的参数数量 template <class Ret, class... Args, class U, class I> auto invoke_with_stack(Ret (*func)(Args...), U* stack, I& stackidx, I& stacksize) { return invoke_with_n_pops(func, stack, stackidx, stacksize, make_index_sequence<sizeof...(Args)>{}); } // 场景B实现:编译期指定调用次数N template <size_t N, class Func, class U, class I> auto invoke_with_stack_n(Func func, U* stack, I& stackidx, I& stacksize) { return invoke_with_n_pops(func, stack, stackidx, stacksize, make_index_sequence<N>{}); }
3. 使用示例
#include <cmath> // 仅用于示例获取sin、atan2函数指针,不属于约束禁止的STL使用 int main() { float outputstack[2] = {3.1415926f/2, 1.0f}; int o_stackidx = 2; int o_stacksize = 2; // 场景A用法:自动识别函数参数数量调用 float atan2_val = invoke_with_stack(&atan2, outputstack, o_stackidx, o_stacksize); // 等价于:atan2(pop_t(outputstack, o_stackidx, o_stacksize), pop_t(outputstack, o_stackidx, o_stacksize)) o_stackidx = 1; o_stacksize = 1; float sin_val = invoke_with_stack(&sin, outputstack, o_stackidx, o_stacksize); // 等价于:sin(pop_t(outputstack, o_stackidx, o_stacksize)) // 场景B用法:编译期指定调用次数为2 o_stackidx = 2; o_stacksize = 2; float custom_val = invoke_with_stack_n<2>((float(*)(float,float))&atan2, outputstack, o_stackidx, o_stacksize); return 0; }
注意事项
- 不要将函数指针强制转为
float(...)类型的可变参数函数指针再调用,类型不匹配会触发未定义行为,必须保证调用时的函数类型和实际目标函数类型完全一致。 - 场景B仅支持编译期常量的次数,运行时传入的动态次数无法实现,因为编译器无法在编译阶段确定需要生成多少个参数。
- 所有模板展开都在编译期完成,没有运行时循环或递归开销,也没有用到任何STL的容器、算法组件,完全符合约束要求。
内容的提问来源于stack exchange,提问作者Nullpoint
相关产品推荐
相关产品推荐

