持续创作,加速成长!这是我参与「掘金日新计划 · 6 月更文挑战」的第13天,点击查看活动详情
类模板参数包
可变参数模板类实际上就是一个模板类,参数是可变的,上一篇我们主要讲的都是函数模板参数包,除了函数模板的使用外,类模板也可以使用不定参数的模板参数,最典型的就是tuple
类了。元组类std::tuple就是一个可变参数的模板类。
可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要复杂。下面我们就来介绍一下这两种方式
继承方式展开
首先我们以std::tuple来举例说明,其大致代码如下:
#include <iostream>
// 可变参数模板类 继承方式展开参数包
// 可变参数模板类的展开一般需要定义2 ~ 3个类,包含类声明和特化的模板类
// 变长模板的声明
template<typename... Values> class tuple;
// 边界条件,特化递归终止条件
template<> class tuple<> {};
// 递归的偏特化定义
template<typename Head, typename... Tail>
class tuple<Head, Tail...> : private tuple<Tail...>
{
typedef tuple<Tail...> inherited;
public:
tuple() {}
//当实例化对象时,则会引起基类的递归构造
tuple(Head v, Tail... vtail) : m_head(v), inherited(vtail...) {
std::cout << "construt: " << v << std::endl;
}
Head& head() {return m_head;}
// 这个this指针很关键,不清楚的朋友可以自己查一下
// 继承类的this其实是指向它的基类。
inherited& tail() {return *this;}
protected:
Head m_head;
};
int main() {
tuple<int, float, std::string> t(1, 2.3, "hello");
std::cout << t.head() << " "
<< t.tail().head() << " "
<< t.tail().tail().head() << std::endl;
return 0;
}
可变参数模板类在定义时一般需要2-3个类。主要包含了3个部分,第一部分是前向声明,声明了一个可变参数的模板类。第二部分是类的定义,在第二部分中实现了部分可展开的参数模板类。第三部分就是就是特化的递归终止类。
根据上面的代码可以得知,tuple类继承除首之外的其他参数的子tuple类,以此类推,最终继承空参数的tuple类。继承关系可以表述为:
tuple<>
↑
tuple<std::string>
string "hello"
↑
tuple<float, std::string>
float 2.3
↑
tuple<int, float, std::string>
int 1
上面比较关键的接口就是tail()成员函数,tail()函数返回的是父类对象,父类对象和子类对象的内存起始地址其实是一样的,因此返回*this
,再强行转化为inherited类型。
但是,像上面使用tail()这样方式取出tuple中的元素是一个比较复杂的操作,需要不断地取tail,最终取head才能获得。标准库的std::tuple,对此进行简化,还提供了一些其他的函数来进行对tuple的访问。例如:
#include <iostream>
#include <tuple>
int main() {
std::tuple<int, float, std::string> t1(1, 2.3, "hello");
std::get<0>(t1) = 4; // 修改tuple内的元素
// 使用get方法获取tuple内的元素
std::cout << std::get<0>(t1) << " "
<< std::get<1>(t1) << " "
<< std::get<2>(t1) << std::endl;
// make_tuple方法生成tuple对象
auto t2 = std::make_tuple(2, 3.4, "World");
// 获取tuple对象元素的个数
std::cout << std::tuple_size<decltype(t2)>::value << std::endl;
// 获取tuple对象某元素的类型(float)
std::tuple_element<1, decltype(t2)>::type f = 1.2;
return 0;
}
偏特化与递归方式展开
除了上面继承方式打开,还有另外一种展开方式:偏特化和递归方式展开
#include <iostream>
//前向声明
template<typename... Args>
struct SizeOf;
//基本定义
template<typename First, typename... Rest>
struct SizeOf<First, Rest...> {
enum {
value = SizeOf<First>::value + SizeOf<Rest...>::value
};
};
//递归终止
template<typename Last>
struct SizeOf<Last> {
enum { value = sizeof (Last) };
};
int main()
{
SizeOf<int, char> s;
std::cout << s.value << std::endl;
}
程序输出5,即sizeof(int)+sizeof(char)
。可以看到一个基本的可变参数模板应用类也是由三部分组成,前向声明、基本定义和递归终止类。实际上三段式的定义也可以改为两段式,可以将前向声明去掉,这样定义:
#include <iostream>
//前向声明
// template<typename... Args>
// struct SizeOf;
//基本定义
template<typename First, typename... Rest>
// struct SizeOf<First, Rest...> {
struct SizeOf {
enum {
value = SizeOf<First>::value + SizeOf<Rest...>::value
};
};
//递归终止
template<typename Last>
struct SizeOf<Last> {
enum { value = sizeof (Last) };
};
int main()
{
SizeOf<int, char> s;
std::cout << s.value << std::endl;
}
递归终止模板类可以有多种写法,比如上例的递归终止模板类还可以下面这样写,最后两个参数时停止,当然也可以展开到0个参数时递归终止:
// 递归到两个参数时终止
template<typename First, typename Last>
struct SizeOf<First, Last> {
enum{ value = sizeof(First) +sizeof(Last) };
};
可变参数基类
在类的继承体系中,基类也可以是可变参数,如下所示:
#include <iostream>
class Base1 {
public:
void test1() {
std::cout<<"This is base1 class."<<std::endl;
}
};
class Base2 {
public:
void test2() {
std::cout<<"This is base2 class."<<std::endl;
}
};
template<typename... Bases>
class Test : public Bases...{};
int main() {
Test<Base1, Base2> test;
test.test1();
test.test2();
}
上述代码中的public Bases... 语法,基类处出现了可变参数。程序输出:
This is base1 class.
This is base2 class.
总结
可变参数模板类就是一个带可变模板参数的模板类,可变参数模板类的参数包展开的方式和可变参数模板函数的展开方式不同,可变参数模板类的参数包展开需要通过模板特化和继承方式去展开,展开方式比可变参数模板函数要更复杂。