无法直接用std::tuple_size配合std::function获取参数个数,因argument_type仅对0/1参函数定义;正确方法是依赖函数类型+可变模板偏特化实现arity元函数,或c++17起结合std::invoke_result辅助推导。

用 std::tuple_size 配合 std::function 无法直接获取函数参数个数
很多人第一反应是把函数转成 std::function,再用 std::tuple_size<decltype></decltype>,但这是错的:std::function 的 argument_type 只对无参或单参函数有定义,多参时根本不存在这个类型别名,编译直接失败。
真正可行的路只有一条:依赖函数类型本身 + 模板参数推导。核心思路是写一个能匹配任意函数签名的可变参数模板,让编译器帮我们“数”参数。
用可变参数模板偏特化提取参数包长度
最稳定、兼容 C++11 起的方法是定义一个元函数 arity,通过函数指针类型或 lambda 类型的模板参数展开来计数:
template<typename T>
struct arity;
template<typename R, typename... Args>
struct arity<R(Args...)> {
static constexpr size_t value = sizeof...(Args);
};
template<typename R, typename... Args>
struct arity<R(*)(Args...)> {
static constexpr size_t value = sizeof...(Args);
};
template<typename R, typename C, typename... Args>
struct arity<R(C::*)(Args...)> {
static constexpr size_t value = sizeof...(Args);
};
template<typename R, typename C, typename... Args>
struct arity<R(C::*)(Args...) const> {
static constexpr size_t value = sizeof...(Args);
};
使用时注意:
立即学习“C++免费学习笔记(深入)”;
-
arity<decltype>::value</decltype>对普通函数有效 - 成员函数需加
&C::member_func,并确保 const 修饰符匹配 - lambda 表达式不能直接用
decltype(lambda)—— 它是未命名闭包类型,不匹配上面任一偏特化;必须先转成函数指针(仅限无捕获)或用std::function包装后再提取,但后者又绕回了前面的问题
用 std::invoke_result 和参数占位推导(C++17+ 更简洁)
C++17 引入 std::invoke_result,虽然它本身不提供参数个数,但可以和辅助模板组合实现更泛化的提取:
template<typename F, typename = void>
struct arity_v : std::integral_constant<size_t, 0> {};
template<typename F>
struct arity_v<F, std::void_t<decltype(&F::operator())>>
: arity<decltype(&F::operator())> {};
// 然后仍需依赖上面的 arity 特化,只是自动适配了 lambda 和 functor
不过实际中,多数场景下你已知函数类型,直接写 sizeof...(Args) 更轻量。比如封装一个回调注册器时:
template<typename Func>
auto register_callback(Func&& f) {
constexpr size_t N = arity<std::decay_t<Func>>::value;
static_assert(N <= 4, "Too many arguments (max 4 supported)");
// ...
}
这种硬编码限制比运行时检查更早暴露问题。
容易被忽略的坑:重载函数、模板函数、函数对象
所有上述方法都要求传入的是**具体、可确定的类型**:
- 对重载函数名(如
std::sin)直接取地址会报错:ambiguous overload - 模板函数(如
std::make_pair)必须显式指定模板参数才能取地址:&std::make_pair<int double></int> - 带捕获的 lambda 根本没有可转换的函数指针类型,
sizeof...无从下手;只能靠运行时包装(如用std::any存参数列表)或放弃静态计数
如果你真需要统一处理这三类,那就不是纯元编程能解决的了——得靠宏生成特化,或者接受运行时反射(目前标准 C++ 还不支持)。











