C++模板中修改std::function签名 为值传参添加const&限定
问题说明
C++17 支持类模板参数推导(CTAD),当模板参数可以通过构造函数推导时不需要显式指定,比如:
auto f = std::function([](A x){});
这段代码里f的类型会被自动推导为std::function<void(A)>。此时调用f(a)(a是类型为A的左值)时,会先调用A的拷贝构造函数,再调用A的移动构造函数;但直接调用原始lambda时只会触发拷贝构造,不会产生额外的移动构造调用。
要消除这个多余开销,可以显式指定形参为const引用,写法如下:
auto f = std::function<void(const A&)>([](A x){});
现在需要修改原有make_function模板函数,自动实现这个优化:自动为所有按值传递的形参加上const &限定,消除CTAD带来的多余移动构造开销。原有实现如下:
template<typename F> auto make_function(F&& f) { return std::function(std::forward<F>(f)); }
实现方案
核心思路是不依赖std::function的默认CTAD推导,而是先提取传入可调用对象的函数签名,将其中纯按值传递的形参替换为const T&类型,再用调整后的签名显式实例化std::function。
完整实现代码如下:
#include <functional> #include <type_traits> // 类型萃取:转换函数签名,仅将按值传递的非const形参替换为const左值引用 template <typename Sig> struct adjust_signature; // 匹配普通函数签名 template <typename Ret, typename... Args> struct adjust_signature<Ret(Args...)> { using type = Ret( std::conditional_t< // 如果形参本身是引用、或者已经是const限定,不做修改 std::is_reference_v<Args> || std::is_const_v<std::remove_reference_t<Args>>, Args, // 否则将值类型替换为const & const std::remove_cvref_t<Args>& >... ); }; // 匹配const成员函数签名(非mutable lambda的operator()默认是const成员函数) template <typename Ret, typename... Args> struct adjust_signature<Ret(Args...) const> { using type = typename adjust_signature<Ret(Args...)>::type const; }; template <typename F> auto make_function(F&& f) { // 提取可调用对象的operator()对应的函数签名 using call_op_ptr = decltype(&std::remove_cvref_t<F>::operator()); using raw_sig = std::remove_pointer_t<call_op_ptr>; // 生成调整后的签名 using target_sig = typename adjust_signature<raw_sig>::type; // 用调整后的签名显式构造std::function return std::function<target_sig>(std::forward<F>(f)); }
实现说明
- 这个实现只会修改纯按值传递的形参:如果原可调用对象的形参本身是左值引用、右值引用,或者已经带const限定,都会保留原有类型,不会破坏原有的参数传递语义。
- 调整签名后,std::function的调用运算符会按const引用接收实参,再传给内部持有的lambda,lambda本身按值接收参数的逻辑不变,只会触发一次拷贝构造,不会产生额外的移动开销。
- 对于重载了多个operator()的可调用对象(比如自定义的带多个调用运算符的类),这个实现会编译报错,这类场景本身也无法自动推导唯一签名,需要用户显式指定目标函数类型,不属于通用逻辑需要覆盖的范围。
内容的提问来源于stack exchange,提问作者sunmat
相关产品推荐
相关产品推荐

