今天才想到其实类似于 int(int, int) 在 C++ 里是内置的类型.(我本以为是什么 STL 黑魔法)
那么把它作为模板的 typename 参数自然就是合理的。这有什么用呢?我们知道,C++ 有个 std::function,是对函数的一个抽象再封装。我们就可以自己实现一个 std::function。
然后就有了下面的 f_wrapper 实现:

第一个是一个普通的参数包模板,当然我们不会去用它。
定义它的目的只是为了底下那段偏特化。这种技法我之前其实也使用过多次,如今只是故伎重施罢了。
偏特化版本的模板,看上去吓人,其实仔细看看还好。
- 第一个 public 成员是构造函数,接受函数指针或 lambda,用来初始化自身;
- 第二个 public 成员是赋值操作符,没什么好说的;
- 第三个 public 成员是括号操作符,用来调用被 wrap 的函数。(最好是在传参的时候用一下 std::forward)
- private 成员存放函数指针。
于是我们可以这么使用:

但你一定注意到我们在初始化的时候用了构造函数而非 lambda 到 f_wrapper 的 cast。
那么我们怎么实现这样的功能呢?
C++ 11 std::is_convertible + std::enable_if
template <typename _Tp, typename = std::enable_if<std::is_convertible<_Tp, _Ret(*)(_Params...)>::value>::type> f_wrapper(_Tp &&fp): _fp(fp) { };
利用模板作为参数,匹配一切的变量。利用 enable_if,来进行一次编译期判断,如果 _Tp 可以 cast 成 _Ret(*)(_Params…),那么就调用这个函数,否则不调用。这样就可以将 lambda 表达式 cast 成我们的 f_wrapper 了。
如果用 C++ 17 的话这里的 is_convertible<_Tp, _Ret(*)(_Params…)>::value 可以改成 is_convertible_v<_Tp, _Ret(*)(_Params…)>。(但加宏加麻了开摆了)
C++ 20 std::convertible_to
C++ 20 带来了强大的 concepts 库,使得我们可以避免许多丑陋的 enable_if。如果我们用 convertible_to 这个 concept,那么代码就可以进一步简化:
f_wrapper(std::convertible_to<_Ret(*)(_Params...)> auto &&fp) : _fp(fp) { };
加上宏,于是完整代码如下:(这里我把 std::forward 写上了)
template <typename... _Tp> class f_wrapper { f_wrapper() = delete; }; template <typename _Ret, typename... _Params> class f_wrapper<_Ret(_Params...)> { using type = f_wrapper<_Ret(_Params...)>; using value_type = _Ret(*)(_Params...); public: #if __cplusplus >= 201103L && __cplusplus < 202002L template <typename _Tp, typename = std::enable_if<std::is_convertible<_Tp, value_type>::value>::type> f_wrapper(_Tp #elif __cplusplus >= 202002L f_wrapper(std::convertible_to<_Ret(*)(_Params...)> auto #endif &&fp) : _fp(std::move(fp)) { }; type &operator=(value_type fp) { _fp = fp; }; constexpr _Ret operator()(_Params&&... param) const { return _fp(std::forward<_Params>(param)...); }; private: value_type _fp = nullptr; };

说良好其实也不良好
C++的 std::function 支持与 std::bind 的 synergy。像这样:

我的 f_wrapper 就没办法支持 XD.
等我什么时候理解 std::bind 了再补上。
还有一个问题,我这个支持不了仿函数,so sad. 可能需要用到 invoke
2021/11/11 更新:
今天看到,std::function 支持各种各样的函数,本质上是一种多态。之前怎么想也没想到该怎么实现仿函数的包装,只要用上这个思想,那就非常容易了。
需要头文件 utility 和 type_traits。
首先写一写 helper class:
namespace __function_helper_class_details__ { template <typename _Ret, typename... _Params> class __function_helper_class_base__ { public: __function_helper_class_base__() = default; virtual constexpr _Ret __invoke(_Params&&... params) = 0; virtual ~__function_helper_class_base__() = default; }; template <typename _Callable, typename _Ret, typename... _Params> class __function_helper_class__ : public __function_helper_class_base__<_Ret, _Params...> { public: __function_helper_class__(_Callable _op) : op(_op) { } constexpr _Ret __invoke(_Params&&... params) override { return op(std::forward<_Params>(params)...); }; private: _Callable op; }; }
父类 function_helper_class_base 是一个纯虚类,只提供相应的接口,子类 function_helper_class 继承这些接口,并且提供正确的实现。我们的可调用对象被存储在子类中。
这个 idea 是非常非常巧妙的。编译期的多态(模板)加上运行时的多态,等于无限种可能。
然后是 Function 的实现:
template <typename _Tp> class Function { Function() = delete; }; template <typename _Ret, typename... _Params> class Function<_Ret(_Params...)> { private: using type = Function<_Ret(_Params...)>; using value_type = _Ret(*)(_Params...); using helper_class_base = __function_helper_class_details__ ::__function_helper_class_base__<_Ret, _Params...>; template <typename _U> using helper_class = __function_helper_class_details__ ::__function_helper_class__<_U, _Ret, _Params...>; helper_class_base *impl; template <typename _Callable> using package_type = std::conditional_t< std::is_same_v<decltype(&_Callable::operator()), _Ret(_Callable::*)(_Params...)>, _Callable, std::conditional_t< std::is_convertible_v<_Callable, _Ret(*)(_Params...)>, _Ret(*)(_Params...), void>>; public: using return_type = _Ret; explicit Function(): impl(nullptr) { } template <typename _Callable, typename _Type = package_type<_Callable>> Function(_Callable _op) : impl(new helper_class<_Type>(_op)) { } ~Function() { delete impl; } template <typename _Callable, typename _Type = package_type<_Callable>> type &operator=(_Callable _op) { delete impl; impl = new helper_class<_Type>(_op); return *this; } constexpr _Ret operator()(_Params&&... params) const { return impl->__invoke(std::forward<_Params>(params)...); } };
有几个细节:
- 读者一定注意到了很长那一串 package_type,并且可能会觉得它没必要。我们似乎只需要一个 enable_if_t 就可以解决问题了。但实际上不是,这个处理是用来应对匿名函数的。如果要把匿名函数的类型作为 helper_class 的参数,那么是会出现错误的:error: conversion from ‘main()::<lambda(int, double)>’ to non-scalar type ‘Function’ requested. 我们这里不得不把函数指针作为参数类型。
- 同时这里兼具了 SFINAE 的作用,以防参数出错。