C++11: 变参模板和std::tuple

##变参模板Variadic Templates
变参模板(Variadic Templates)顾名思义就是参数类型和个数可以改变的模板。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
//定义
template<typename... Arguments>
class VariadicTemplate;
//实例化的方法
VariadicTemplate<double, float> instance;
VariadicTemplate<bool, unsigned short int, long> instance;
VariadicTemplate<char, std::vector<int>, std::string, std::string, std::vector<long long>> instance;
//参数个数甚至可以为0
VariadicTemplate<> instance;

//变参模板函数
template<typename... Arguments>
void SampleFunction(Arguments... parameters);
//使用
SampleFunction<int, int>(16, 24);
SampleFunction<std::string>("fun");

有人要问了这个省略号C语言里就有嘛,printf不就是不定参数的嘛。但是…但是,变参模板是类型安全的,而且它可以让类似功能的实现得到极大简化。这篇文章就介绍了一个用变参模板实现的非常精巧的类型安全的printf, 还简要的说明了C++11引入这个特性的动机。我对模板元编程不甚了解,但是从大牛们用奇淫技巧实现的boost::mpl和boost::tuple来模拟可变参数模板,不难看出这个功能对编写C++库的重要性。当然如果Concepts不被移出C++11标准,C++泛型能力会有翻天覆地的提高,不管怎样,C++11在语言层级增加了对变参模板支持,还是极大的增强了C++模板的抽象能力。

##std::tuple
对于大多数程序员来说可能很少去编写模板库,但是新的可变参数的容器std::tuple大多数都会用到。tuple就是一个包含任意多个不同类型的数据成员的集合,就像一个增强版的std::pair。直接贴出一些用例,细节参照手册

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
// tuple example
#include <iostream> // std::cout
#include <tuple> // std::tuple, std::get, std::tie, std::ignore

int main ()
{
std::tuple<int,char> foo (10,'x');
auto bar = std::make_tuple ("test", 3.1, 14, 'y');

std::get<2>(bar) = 100; // access element

int myint; char mychar;

std::tie (myint, mychar) = foo; // unpack elements
std::tie (std::ignore, std::ignore, myint, mychar) = bar; // unpack (with ignore)

mychar = std::get<3>(bar);

std::get<0>(foo) = std::get<2>(bar);
std::get<1>(foo) = mychar;

std::cout << "foo contains: ";
std::cout << std::get<0>(foo) << ' ';
std::cout << std::get<1>(foo) << '\n';

return 0;

##实际中应用
虽然变参模板无比拉风,但是平时编程时却不容易用到,tuple灵活强大,但是只用在函数返回值上,也未免大财小用,而且用多了会降低代码的可读性。在项目中,有需求要测试触发signal的功能,需要一个slot的mock,signal 可能传递不同个数不同类型的参数,终于”以权谋私”用上了Variadic Templates和tuple。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
template<typename... Args> 
class SignalReceiverMock {
public:
SignalReceiverMock() : _slotCalled(false) {}

void slot(Args... args) {
_slotCalled = true;
_arguments = std::make_tuple(args...);
}

bool slotCalled() {
return _slotCalled;
}

bool argumentsPassedCorrectly(Args... args) {
auto arguments = std::make_tuple(args...);
return arguments == _arguments;
}

private:
bool _slotCalled;
std::tuple<Args...> _arguments;
};

//在测试中使用
SignalReceiverMock<std::string, int> receiver1;
SignalReceiverMock<> receiver2;
Signal1 signal1("test", 0); // singal1 有传递两个参数
Signal2 signal2(); //signal2 不传递参数
signal1.connect(boost::bind(&SignalReceiverMock<std::string, int>::slot, *receiver1, _1, _2));
signal2.connect(boost::bind(&SignalReceiverMock<>::slot, *receiver2));
....//触发signal的操作
testResult.asserTrue(receiver1.slotCalled() && receiver1.argumentsPassedCorrectly("test", 0));
testResult.asserTrue(receiver2.slotCalled());