类型萃取(trait)的概念我们前面有介绍过。可以将trait看做是一种静态反射技术,通过trait我们可以自动提取出想要的代码元信息,避免让客户代码显示去提供这些信息,从而使得客户代码更加的简洁。
在dates中,客户可使用FakeSystem
定义一个fake系统,与SUT交互。FakeSystem
拥有send
和recv
接口,分别向SUT发送消息,以及从SUT接收消息。send
的入参是一个原型为void(Msg&)
的lambda函数,用于描述如何构造Msg消息。
visitor.send([this](AccessReq& req)
{
req.capability = CAPABILITY;
});
由于一个FakeSystem
可以发送多种msg,所以send
接口无法确定lambda的具体类型,因此send
的参数只能定义为泛型参数,它的原型为:
template<typename BUILDER>
void send(const BUILDER& builder);
这样send
就可以传入各种构造不同消息类型的lambda了,而且还可以调用原型一致的普通函数或者仿函数,客户使用起来非常简洁。
现在我们来实现send
。send
中需要创建一个消息,然后交给builder去构造。如下伪代码:
template<typename BUILDER>
void send(const BUILDER& builder)
{
Msg msg; // 这里Msg到底应该是什么类型?
builder(msg);
// ...
}
上面代码的问题在于,我们不知道Msg的类型!Msg的类型是由客户传入的不同builder决定的,例如visitor.send([this](AccessReq& req){...})
中,Msg是AccessReq
。换句话说,我们需要从传入的lambda表达式的类型中获取Msg的类型。
模板元编程可以帮助我们解决这个问题。还记得我们前面介绍的TLP库中trait工具中的__lambda_para()
吗?于是代码修改如下:
template<typename BUILDER>
void send(const BUILDER& builder)
{
using Msg = __lambda_para(BUILDER, 0); // 获取BUILDER的参数列表中的第一个参数类型
Msg msg;
builder(msg);
// ...
}
如上我们通过类型萃取,从客户传入的函数类型中取出了参数的类型,使得框架的接口保持了简洁和灵活性。