今天才想到其实类似于 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 的作用,以防参数出错。

Offensive77

A little learning is a dangerous thing.

发表评论

您的电子邮箱地址不会被公开。 必填项已用*标注