数MakeIndexes生成一个整形序列,这个整形序列就是IndexSeq<0,1,2>,整形序列代表了tuple中元素的索引,生成整形序列之后再调用print_helper,在print_helper中展开这个整形序列,展开的过程中根据具体的索引从tuple中获取对应的元素,最终将从tuple中取出来的元素组成一个可变模板参数,从而实现了tuple“还原”为可变模板参数,最终调用print打印可变模板参数。
?
tuple在模板元
编程中的另外一个应用场景是用来实现一些编译期算法,比如常见的遍历、查找和合并等算法,实现的思路和可变模板参数实现的编译期算法类似,关于tuple相关的算法,读者可以参考笔者在github上的代码:https://github.com/qicosmos/cosmos/tree/master/tuple。
?
下面来看看模版元的具体应用。
?
6.模版元的应用
?
我们将展示如何通过模版元来实现function_traits和Vairant类型。
?
function_traits用来获取函数语义的可调用对象的一些属性,比如函数类型、返回类型、函数指针类型和参数类型等。下面来看看如何实现function_traits。
?
?
template
struct function_traits;
?
//普通函数
template
struct function_traits
{
public:
? ? enum { arity = sizeof...(Args) };
? ? typedef Ret function_type(Args...);
? ? typedef Ret return_type;
? ? using stl_function_type = std::function;
? ? typedef Ret(*pointer)(Args...);
?
? ? template
? ? struct args
? ? {
? ? ? ? static_assert(I < arity, "index is out of range, index must less than sizeof Args");
? ? ? ? using type = typename std::tuple_element>::type;
? ? };
};
?
//函数指针
template
struct function_traits : function_traits{};
?
//std::function
template
struct function_traits> : function_traits{};
?
//member function
#define FUNCTION_TRAITS(...) \
? ? template \
? ? struct function_traits : function_traits{}; \
?
FUNCTION_TRAITS()
FUNCTION_TRAITS(const)
FUNCTION_TRAITS(volatile)
FUNCTION_TRAITS(const volatile)
?
//函数对象
template
struct function_traits : function_traits{};
?
由于可调用对象可能是普通的函数、函数指针、lambda、std::function和成员函数,所以我们需要针对这些类型分别做偏特化。其中,成员函数的偏特化稍微复杂一点,因为涉及到cv符的处理,这里通过定义一个宏来消除重复的模板类定义。参数类型的获取我们是借助于tuple,将参数转换为tuple类型,然后根据索引来获取对应类型。它的用法比较简单:
?
?
template
void PrintType()
{
? ? cout << typeid(T).name() << endl;
}
int main()
{
? ? std::function f = [](int a){return a; };
? ? PrintType>::function_type>(); //将输出int __cdecl(int)
? ? PrintType>::args<0>::type>();//将输出int
? ? PrintType::function_type>();//将输出int __cdecl(int)
}
?
有了这个function_traits和前面实现的一些元函数,我们就能方便的实现一个“万能类型”—Variant,Variant实际上一个泛化的类型,这个Variant和boost.variant的用法类似。boost.variant的基本用法如下:
?
typedef variant vt;
vt v = 1;
v = 'a';
v = 12.32;
这个variant可以接受已经定义的那些类型,看起来有点类似于
c#和java中的object类型,实际上variant是擦除了类型,要获取它的实际类型的时候就稍显麻烦,需要通过boost.visitor来访问:
?
?
通过
C++11模版元实现的Variant将改进值的获取,将获取实际值的方式改为内置的,即通过下面的方式来访问:
?
typedef Variant cv;
cv v = 10;
v.Visit([&](double i){cout << i << endl; }, [](short i){cout << i << endl; }, [=](int i){cout << i << endl; },[](const string& i){cout << i << endl; });//结果将输出10
这种方式更方便直观。Variant的实现需要借助前文中实现的一些元函数MaxInteger、MaxAlign、Contains和At等等。下面来看看Variant实现的关键代码,完整的代码请读者参考笔者在github上的代码https://github.com/qicosmos/cosmos/blob/master/Varaint.hpp。
?
?View Code
实现Variant首先需要定义一个足够大的缓冲区用来存放不同的类型的值,这个缓类型冲区实际上就是用来擦除类型,不同的类型都通过placement new在这个缓冲区上创建对象,因为类型长度不同,所以需要考虑内存对齐,
C++11刚好提供了内存对齐的缓冲区aligned_storage:
?
template< std::size_t Len, std::size_t Align = /*default-alignment*/ >
struct aligned_storage;
它的第一个参数是缓冲区的长度,第二个参数是缓冲区内存对齐的大小,由于Varaint可以接受多种类型,所以我们需要获取最大的类型长度,保证缓冲区足够大,然后还要获取最大的内存对齐大小,这里我们通过前面实现的MaxInteger和MaxAlign就可以了,Varaint中内存对齐的缓冲区定义如下:
?
enum
{
? ? data_size = IntegerMax::value,
? ? align_size = MaxAlign::value
};
using data_t = typename std::aligned_storage::type; //内存对齐的缓冲区类型
其次,我们还要实现对缓冲区的构造、拷贝、析构和移动,因为Variant重新赋值的时候需要将缓冲区中原来的类型析构掉,拷贝构造和移动构造时则需要拷贝和移动。这里以析构为例,我们需