boost::function_traits检测可调用对象首参类型编译报错排查
错误原因
boost::function_traits原生不支持直接传入lambda、自定义仿函数类型作为模板参数:它的内置特化实现只覆盖普通函数指针、函数引用、std::function这类明确的函数类型,而lambda是编译器生成的匿名仿函数类(重载了operator()),不在默认支持范围内,直接传入就会触发你看到的「隐式实例化未定义模板」「类型不完整」编译错误。- 你的类型转换逻辑没有生效:代码里
Function定义为std::remove_reference_t<Callable>,传入lambda时得到的是lambda本身的类类型,不是指针,后续套std::remove_pointer不会对类型做任何修改,最终传给boost::function_traits的还是原始lambda类型,自然匹配不到对应的特化实现。 - 现有判断逻辑存在隐患:就算解决了类型萃取问题,当前判断只校验首参数是否为
EmitContext,没有校验参数总数是否符合1 + N的要求,后续调用时容易触发参数数量不匹配的编译错误。
修复方案
最稳妥的方式是放弃依赖boost::function_traits,直接实现编译期可调用性检测,自动匹配回调的调用形式,兼容所有可调用对象类型,以C++17为例:
- 首先在
CallbackImpl类内添加编译期检测逻辑,判断当前持有的可调用对象是否支持接收EmitContext作为首参数:
template<typename F, typename = void> struct can_take_context : std::false_type {}; template<typename F> struct can_take_context<F, std::void_t< decltype(std::declval<F>()(std::declval<const EmitContext&>(), std::declval<const Args&>()...)) >> : std::true_type {};
- 替换原来
OnEvent重载里的判断逻辑,不需要再萃取元数和首参数类型:
void OnEvent( const EmitContext& emit_context, const Args&... args ) override { if constexpr( can_take_context<Function>::value ) { Call( mFunc, emit_context, args... ); } else { Call( mFunc, args... ); } }
这种实现会在编译期直接尝试调用可调用对象,传入EmitContext加所有事件参数,如果调用合法就走带上下文的分支,否则走普通参数分支,不管传入的是lambda、普通函数、std::function还是自定义仿函数都能正确适配,也不会出现boost库对lambda支持不足的问题。
如果一定要使用boost::function_traits,需要先萃取可调用对象operator()的对应函数类型,再传给trait,对lambda、仿函数类型要单独处理成员函数指针的萃取逻辑,写法冗余很多,不推荐使用。
内容的提问来源于stack exchange,提问作者Crazy Sage
相关产品推荐
相关产品推荐

