## 需求描述
假设我们现在有一个异步请求,当请求返回时携带一些参数调用回调函数,但同时我们有一些需要本地传输的参数,比如本次请求的请求`ID`号,总不能传过去再传回来,既浪费带宽也浪费序列化的算力
因此我们的回调函数的参数会是两段,一段本地参数,一段远程返回的参数,形如
```cpp
RetType CallBack(LocalArg1, ... , RemoteArg1, ...);
```
很自然地我们想到了使用`std::bind`将本地参数绑定,让`CallBack`变成
```cpp
auto NewCallBack = std::bind(CallBack, LocalArg1, ... , std::placeholders::_1,std::placeholders::_2, ...);
```
这样回调时直接传入远程返回的参数即可
```cpp
NewCallBack(RemoteArg1, ...);
```
那么问题来了,标准库自带的`std::placeholders`并不是无限的,且都是编号的,我们总不能写一堆不同本地参数和远程参数数量的自定义`bind`函数出来,那也太不优雅了且一旦参数真的很多,超过了`std::placeholders`的数量就会编译失败,所以我们需要一个方法去生成递增编号指定数量的`placeholders`
## 制作一个自定义placeholder
对标准库来说,只要`std::is_placeholder<T>`为真,`T`就可以被当做`placeholder`使用
```cpp
template <int N> struct variable_placeholder {};
namespace std {
template <int N>
struct std::is_placeholder<variable_placeholder<N>>
: public integral_constant<int, N + 1> {};
}
```
这个`namespace std`看着有点吓人,虽然还不知道为什么,但是从一篇文章[Don't reopen \`namespace std\` – Arthur O'Dwyer – Stuff mostly about C++](https://quuxplusone.github.io/blog/2021/10/27/dont-reopen-namespace-std/)里看到了另一种方式,我们还是改一下先
```cpp
template<int N>
struct std::is_placeholder<variable_placeholder<N>> : std::integral_constant<int, N + 1>
{
};
```
这样我们的自定义`placeholder`就完成了
## 生成指定数量的placeholder
`f`为回调函数,`sizeof...(Is)`为要生成的`placeholder`个数,`Args...`为本地参数包
将本地参数包在前半部分展开,将自定义`placeholder`在后半部分展开,即可达到目的
```cpp
template <std::size_t... Is, typename F, typename... Args>
auto bind_helper(std::index_sequence<Is...>, F f, Args &&...args) {
return std::bind(f, std::forward<Args>(args)...,
variable_placeholder<Is>{}...);
}
```
使用姿势如下,通过`std::make_index_sequence`生成指定个数的`std::index_sequence`,给`say`函数绑定了第一个字符串,调用时传入了第二个字符串,输出为`Hello, world!`,由于调用时传入了1个参数,所以是`std::make_index_sequence<1>{}`
```cpp
void say(const char *first_word, const char *second_word) {
std::cout << std::format("{}, {}!\n", first_word, second_word);
}
auto hello = bind_helper(std::make_index_sequence<1>{}, say, "Hello");
hello("world");
```
## 自动生成合适数量的placeholder
参数列表由两部分组成,需要对其中一部分每一个参数生成一个`placeholder`
若函数所有参数为`Vars`,本地参数为`Args`,则需要生成的`placeholder`数量为`sizeof...(Vars) - sizeof...(Args)`,对`bind_helper`封装如下
```cpp
template <typename RetType, typename... Args, typename... Vars>
auto bind(std::function<RetType(Vars...)> f, Args &&...args) {
return bind_helper(
std::make_index_sequence<sizeof...(Vars) - sizeof...(Args)>{}, f,
std::forward<Args>(args)...);
}
```
现在`bind say`可以这么写
```cpp
auto hello = bind(say, "Hello");
hello("world");
```
## 接受Lambda
也许是上面的`bind`写的不优雅,当我想用`Lambda`做`bind`的时候遇到了麻烦,它无法被`std::function`接受,无奈之下只好封装了一层(也许有更好的方法?)
```cpp
template <typename T, typename... Args> auto bind(T f, Args &&...args) {
return bind(std::function(f), std::forward<Args>(args)...);
}
```
搞定
```cpp
bind(
[](const char *first_word, const char *second_word) {
std::cout << std::format("{}, {}!\n", first_word, second_word);
},
"Hello")("world");
```
## 接受成员函数
由于成员函数需要对象才可以调用,与普通函数不同的是调用成员函数时第一个参数是该成员函数所属类的对象
```cpp
template <typename Class, typename RetType, typename... Vars, typename... Args>
auto bind(RetType (Class::*f)(Vars...), Class &obj, Args &&...args) {
return bind_helper(
std::make_index_sequence<sizeof...(Vars) - sizeof...(Args)>{}, f, obj,
std::forward<Args>(args)...);
}
```
```cpp
class A {
public:
void say(const char *first_word, const char *second_word) {
std::cout << std::format("{}, {}!\n", first_word, second_word);
}
};
A a;
bind(&A::say, a, "Hello")("world");
```
到这里我们自己的`bind`之旅就结束了