本文不是一篇对std::bind
的源码分析,而是试图通过逐步推导的方式,不断迭代优化,最终实现一版能阐述清核心原理的demo。非常像真实的开发过程。
事实上,关于std::bind
的源码分析已有优质的讲解,建议想深入了解的读者参阅。
什么是std::bind
?
std::bind 是 C++ 标准库中的一个函数模板,它用于创建一个可调用对象(callable object)。通过 std::bind,可以将一个函数与其参数绑定为一个可调用对象,从而延迟函数的调用或者改变函数的调用方式。下面是个例子:
#include <iostream>
#include <functional>
void printSum(int a, int b) {
std::cout << "Sum: " << a + b << std::endl;
}
int main() {
auto bindFunc = std::bind(printSum, 10, 20);
bindFunc(); // 调用绑定的函数
return 0;
}
实现
明确目的
bind
函数接受一个callable实例(可能是函数指针、lambda或functor等),以及一系列不定参数;返回一个有着新签名(signature)的callable实例。据此我们可以给出第一版设计(注意并不能编译):
template<typename TFunc, typename ...TArgs>
struct bind_result {
explicit bind_result(TFunc func, TArgs ...args)
: func(std::move(func)), args(args...)
{}
template<typename ...TRealArgs>
void operator()(TRealArgs ...real_args) {
func(args.../* not compile */, real_args...);
}
private:
TFunc func;
std::tuple<TArgs...> args;
};
问题在于我们无法直接申明模板函数包(template argument pack)类型的成员变量,只能用std::tuple
包一层;而std::tuple
无法使用折叠表达式(fold expression)语法来展开。这样导致注释处出现编译错误。我们需要解决展开std::tuple
的问题。
展开tuple
恰巧笔者在SO上读到这篇文章,里面提供了将tuple展开的思路,先给出实现,再进行解释。
template<int ...i>
struct seq {};
template<int i, int ...pack>
struct gen_seq {
using type = typename gen_seq<i - 1, i - 1, pack...>::type;
};
template<int ...pack>
struct gen_seq<0, pack...> {
using type = seq<pack...>;
};
下面对该trait举例说明。考虑gen_seq<3>
这个例子,其模板参数列表(template<int i, int ...pack>
)中的i等于3,pack为空,那么using type = typename gen_seq<i - 1, i - 1, pack...>::type
会被解释为using type = gen_seq<2, 2>::type
。以此类推,gen_seq<2, 2>::type会被最终解释为seq<0, 1, 2>
。
以下例子展示了展开tuple的全流程,理解此例子有助于理解后面的代码:
template<int ...i>
void print_i(seq<i...>) {
/* fold expression in C++17 */
(printf("%d ", i), ...);
}
int main() {
print_i(gen_seq<3>::type{}); // (1)
}
解释:标注(1)处利用gen_seq
创建seq<0,1,2>
实例,该实例变量又指导编译器推理出模板参数,最终利用折叠表达式(fold expression)逐个将0,1,2
在console中输出。
阶段性成果
更新bind_result
,解决编译错误,我们可以得到一个能work的阶段性结果。值得一提的是,我们的bind尚且存在许多局限性,还无法应用于生产环境,后面的文章会继续优化。个人觉得不断解决新的挑战、不断重构才是编程的乐趣。
template<typename TFunc, typename ...TArgs>
struct bind_result {
explicit bind_result(TFunc func, TArgs ...args)
: func(std::move(func)), args(args...)
{}
template<typename ...TRealArgs>
void operator()(TRealArgs ...real_args) {
internal_call(typename gen_seq<sizeof...(TArgs)>::type(), real_args...);
}
private:
TFunc func;
std::tuple<TArgs...> args;
private:
template<int ...i, typename ...TRealArgs>
void internal_call(seq<i...>, TRealArgs ...real_args) {
func(std::get<i>(args)..., real_args...);
}
};
测试样例1(绑定lambda -> OK):
int main() {
auto f = [](int i, int j){
std::cout << "i: " << i << "; j: " << j;
};
bind_result br(f, 1);
br(2);
}
测试样例2(绑定function -> OK):
void f(int i, int j) {
std::cout << "i: " << i << "; j: " << j;
}
int main() {
bind_result br(&f, 1);
br(2);
}
测试样例3(绑定method -> Err):
struct foo {
void f(int i, int j) {
std::cout << "i: " << i << "; j: " << j;
}
};
int main() {
bind_result br(&foo::f, 1);
br(2); // unlike other managed language, member func can't be simply invoked like
// a function pointer, otherwise, you should invoke it with an instance
// variable.
}
测试样例4(绑定带返回值的callable对象 -> Err):
int main() {
auto f_sum = [](int i, int j){
return i + j;
};
bind_result f_sum_by_1(f_sum, 1);
auto res = f_sum_by_1(2); // f_sum_by_1 returns void.
}
标签:std,...,args,seq,int,bind,work
From: https://www.cnblogs.com/sherlockcai/p/17926878.html