目录
一、本文目的
上一篇介绍了std::function和std::bind实现方式,可以保存一般函数和成员函数,但并未实现对参数包的保存。由于参数包的保存也是一个难点,所以将该内容作为一个单独的篇幅进行介绍。通过该篇可以了解参数包的实现原理,同时也能掌握C++模板的实现技巧。
二、std::bind如何绑定参数
首先我们先了解下,std::bind的用法,看它是如何工作的?
std::bind可以不绑定任何参数,也可以只绑定部分参数。不绑定参数得到的std::function调用时,需要将所有参数传入,std::function模板实参为完整函数形式。 如上面例子std::function<int(int,int,int)>。
如果是绑定部分参数,也就是部分参数指定,其他参数待定(调用时传入),需要在绑定时指定固定参数,其他参数使用占位符代替。占位符个数就是std::function模板形参个数,类型必须一致。总结如下:
- 固定参数位置对应函数形参位置;
- 占位符必须从_1开始排列,占位符的序号就是函数参数位置顺序序号(不包括固定参数);
- 可以指定占位符顺序,占位符顺序决定了std::function模板形参顺序;可以认为bind自定义封装了另外一个函数;
- 调用时顺序和占位符保持一致。
class Print
{
public:
Print() {}
virtual ~Print() {}
void draw(int a, double b, float c)
{
std::cout << "a = " << a << "," << "b = " << b << "," << "c = " << c << "\n";
}
};
int main()
{
Print pt;
// std::placeholders::_1->a,1->b,std::placeholders::_2->c; 占位符顺和函数参数顺序保持一致
// std::function形参顺序和bind中占位符顺序保持一致
std::function<void(float, int)> f2 = std::bind(&Print::draw, &pt, std::placeholders::_2, 1, std::placeholders::_1);
f2(2.2, 3.5); // 输出a = 3,b = 1,c = 2.2
system("pause");
return 0;
}
三、参数包如何保存
要保存参数包,想当然是如下写法:
// 错误代码
template<typename Fx, typename... Args>
class _binder_
{
public:
_binder_(Fx f, Args... args) :m_f(f), m_args(args...) {}
auto operator()()
{
return (*m_f)(args...);
}
private:
Fx m_f;
Args... m_args; // 错误写法
};
但是Args... m_args没有这种写法,无法编译通过。所以需要考虑另外一种保存参数包的方法。C++11中的tuple可以保存不同类型的参数,可以使用它。如下:
template<typename Fx, typename... Args>
class _binder_
{
public:
_binder_(Fx f, Args... args) :m_f(f), m_tu(args...) {}
auto operator()()
{
// (*m_f)(?)
return 0;
}
private:
Fx m_f;
tuple<Args...> m_tu;
};
那么如何使用m_f调用m_tu呢?调用形式必须为(*m_f)(args...),实参必须是包展开形式,不能直接为m_tu,(*m_f)(m_tu)这种写法是错误的。所以必须对m_tu进行展开调用。这里先讲一下如何对tuple进行展开。
四、tuple展开
考虑如下代码:
class Print
{
public:
template<typename... Args>
void print(Args... args)
{
tuple<Args...> tu(args...);
for (int i = 0; i < sizeof(tu); i++)
cout << std::get<i>(tu); // 错误
}
};
int main()
{
Print pt;
pt.print(1, "cjf", 'c');
system("pause");
return 0;
}
该代码想传入任意类型参数将其打印出来。在Print::print中将参数包转化为tuple,企图通过遍历tuple实现打印各个参数。上述代码是错误的,原因在于std::get使用,get中的<>数值必须是编译常量,是不能接受变量的。可以改为下面这种方式:
template<typename... Args>
class Print
{
public:
Print(Args... args) :m_tu(args...) {}
template<size_t... I>
void print()
{
int arry[] = { (cout << std::get<I>(m_tu),0)... };
}
private:
tuple<Args...> m_tu;
};
template<typename... Args>
Print<Args...> make_print(Args... args)
{
return Print<Args...>(args...);
}
int main()
{
auto _print = make_print(1, "cjf", 'c');
_print.print<0, 1, 2>();
system("pause");
return 0;
}
上面构造了模板函数template<0,1,2> void print(),调用(cout << std::get<I>(m_tu),0)...,会展开成(cout<<std::get<0>(m_tu),0),(cout<<std::get<1>(m_tu),0),(cout<<std::get<2>(m_tu),0)形式,括号0会作为数组元素值。
通过make_print模板传入Args...构造模板实例,避免手动写,太过复杂。上面问题在于,我们要手动指定tuple的所有索引,如果数组个数是100个,根本无法写。最好的调用形式为_print.print()。
事实上,我们在构造Print时,传入Args...参数包,是知道参数个数的,问题是如何在编译时确定template<0,1,2> void print()模板,也就是必须产生template<0,1,2...>这样的索引系列。
五、构造索引系列_index_seq_
现在关键问题是如何产生索引序列(模板)。这里用到了类模板递归继承技巧,直接贴出代码:
template<size_t... I>
struct _index_seq_ {};
template<size_t N, size_t... I>
class _make_index_seq_ :public _make_index_seq_<N - 1, N - 1, I...> {};
template<size_t... I>
class _make_index_seq_<0, I...> :public _index_seq_<I...> {};
_index_seq_为索引序列。_make_index_seq_用来产生索引序列,当我们传入_make_index_seq_<3>()时,会产生如下编译结果:
- 先产生template<3> class _make_index_seq_;
- 而template<5> class _make_index_seq_继承t_make_index_seq_<3-1,3-1>继而会产生模板实例template<2,2> _make_index_seq_;
- 同理template<2,2> _make_index_seq_会继承_make_index_seq_<1,1,2>;
- _make_index_seq_<1,1,2>继承_make_index_seq_<0,0,1,2>;
- _make_index_seq_<0,0,1,2>继承_index_seq_<0,1,2>结束。
- 也就时_make_index_seq_<3>继承了_index_seq_<0,1,2>。
六、通过_index_seq_展开tuple
根据上面方法,代码改为如下:
template<size_t... I>
struct _index_seq_ {};
template<size_t N, size_t... I>
class _make_index_seq_ :public _make_index_seq_<N - 1, N - 1, I...> {};
template<size_t... I>
class _make_index_seq_<0, I...> :public _index_seq_<I...> {};
template<typename... Args>
class Print
{
public:
Print(Args... args) :m_tu(args...) {}
void operator()()
{
print(m_seq);
}
template<size_t... I>
void print(_index_seq_<I...>)
{
int arry[] = { (cout << std::get<I>(m_tu),0)... };
}
private:
_make_index_seq_<sizeof...(Args)> m_seq;
tuple<Args...> m_tu;
};
template<typename... Args>
Print<Args...> make_print(Args... args)
{
return Print<Args...>(args...);
}
int main()
{
auto _print = make_print(1, "cjf", 'c');
_print();
system("pause");
return 0;
}
在Print构造函数中添加了_make_index_seq_<sizeof...(Args)> m_seq,编译时会自动产生_index_seq_。在Print中添加仿函数,调用print(m_seq),将其m_seq传给_index_seq_,因为_make_index_seq_<N>继承了_index_seq_<0,1,2,...,N-1>,所以可以直接将m_seq赋给_index_seq_。
事实上,C++14已经为我们准备了interger_sequence、index_sequence、make_interger_sequence、make_index_sequence,大家有兴趣可以参考:https://en.cppreference.com/w/cpp/utility/integer_sequence。
七、binder保存参数
按照上面方式,我们现在可以将第一篇中的_binder_和_mbinder_来保存参数包(所以参数),部分参数保存将在下一篇幅介绍。代码如下:
#pragma once
#include <tuple>
template<size_t... I>
struct _index_seq_ {};
template<size_t N, size_t... I>
class _make_index_seq_: public _make_index_seq_<N-1, N-1, I...> {};
template<size_t... I>
class _make_index_seq_<0, I...> : public _index_seq_<I...> {};
//
template<typename Fx, typename... Args>
class _binder_
{
public:
_binder_(Fx f, Args... args) :m_f(f),m_tu(args...) {}
auto operator()()
{
return call(m_seq);
}
template<size_t... I>
auto call(_index_seq_<I...>)
{
return (*m_f)(std::get<I>(m_tu)...);
}
private:
std::tuple<Args...> m_tu;
_make_index_seq_<sizeof...(Args)> m_seq;
Fx m_f;
};
template<typename Fx, typename T, typename... Args>
class _mbinder_
{
public:
_mbinder_(Fx f, T *t, Args... args) :m_f(f),m_t(t),m_tu(args...) {}
auto operator()()
{
return call(m_seq);
}
template<size_t... I>
auto call(_index_seq_<I...>)
{
return (m_t->*m_f)(std::get<I>(m_tu)...);
}
private:
std::tuple<Args...> m_tu;
_make_index_seq_<sizeof...(Args)> m_seq;
Fx m_f;
T *m_t;
};
//
// 一般函数bind
template<typename Fx, typename... Args>
auto _bind(Fx f, Args... args)
{
return _binder_<Fx, Args...>(f, args...);
}
// 成员函数bind
template<typename Fx, typename T, typename... Args>
auto _bind(Fx f, T *t, Args... args)
{
return _mbinder_<Fx, T, Args...>(f, t, args...);
}
//
template<typename R, typename... Args>
class binder_wrapper_impl
{
public:
virtual R call() { return R(); }
};
template<typename _binderType, typename R, typename... Args>
class binder_wrapper:public binder_wrapper_impl<R, Args...>
{
public:
binder_wrapper(_binderType binder) :m_binder(binder) {}
virtual R call()
{
return m_binder();
}
private:
_binderType m_binder;
};
// 泛化版本
template<typename R, typename... Args>
class _function;
// 偏特例化
template<typename R, typename... Args>
class _function<R(Args...)>
{
public:
template<typename _binderType>
_function(_binderType binder)
{
m_binder = new binder_wrapper<_binderType, R, Args...>(binder);
}
virtual ~_function() {
delete m_binder;
}
auto operator()()
{
return m_binder->call();
}
private:
binder_wrapper_impl<R, Args...> *m_binder = nullptr;
};
调用如下:
class Print
{
public:
int draw(int a, int b, int c)
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
return 0;
}
};
int print(int a, int b, int c)
{
cout << "a = " << a << ", b = " << b << ", c = " << c << endl;
return 0;
}
int main()
{
_function<int(int, int, int)> f = _bind(&print, 1, 2, 3);
f();
Print pt;
_function<int(int, int, int)> f2 = _bind(&Print::draw, &pt, 1, 2, 3);
f2();
system("pause");
return 0;
}