一 可变参数模板 variadic template
前面的章节 C++ 学习系列 -- 模板 template-CSDN博客 我们介绍了 c++ 中的模板概念,本章则在其基础上介绍了新的概念 可变参数模板 variadic template ,顾名思义,可变参数模板意思为模板参数的类型与数量是变化的,比如:
cpp
template<typename ...Args>
void print(Args... args);
template<typename ...Args>
class my_class;
1.1 可变参数模板函数
cpp
#include<iostream>
// 递归终止调用的 Print 模板参数个数为 0
void Print()
{
std::cout << " " << std::endl;
}
template<typename T, typename ... Args>
void Print(T t, Args...args)
{
std::cout << t << " "; // 打印出参数 t 的值
Print(args...); // 递归调用 Print,将除了 t 剩下的一包参数丢入 Print
}
int main()
{
Print("abc", 'a', 1.0, 66);
return 0;
}
输出:
1.2 可变参数模板递归继承类
cpp
// my_class.h
#include<iostream>
template<typename ...Args>
class MyClass;
// 递归类终止时,template 的参数包为空
template<>
class MyClass<>
{
public:
MyClass()
{
std::cout << "MyClass constructor. " << std::endl;
}
};
// 递归继承 MyClass 类,基类比派生类的模板参数包少一个 参数 T
template<typename T, typename ...Args>
class MyClass<T, Args...> : private MyClass<Args...>
{
public:
MyClass(T t, Args... args):data(t),MyClass<Args...>(args...)
{
std::cout << "MyClass constructor sizeof(Args) " << sizeof... (Args) << ", data: " << data << std::endl;
}
private:
T data;
};
// main.cpp
#include"my_class.h"
int main()
{
MyClass<double, float, int, long, std::string> my_class(1.22, 1.6, 66, 88, "abcd");
return 0;
}
输出:
cpp
template<>
class MyClass<>;
由输出可以看出,递归继承可变参数的类的模板参数包大小,从 4 -> 3 -> 2 -> 1 -> 0
,最后递归终止时调用的是一个空模板参数包的类
二 tuple
1.1 tuple 简介
在 tuple出现之前,c++ 中的容器,序列容器:vector、deque、list ,关联容器:set、map 等,所存储的元素类型都是单一的(map 的 所有的 key 类型是相同的, 所有的 value 类型是相同的 )。
如果我们有这样一个需求,需要一个容器或者说用着类似于容器的一个东西,可以存储不同类型的元素,上面的容器是无法满足我们的需求的。考虑到如此,c++ 为我们提供了 pair 类,但是美中不足的是,pair 只能存储两个元素,若是存储两个以上的元素呢? pair 就帮不了我们了。
在 c++11 之前,前面的需求是无法满足的,c++11则提供了 元组 std::tuple 这个概念(元组在其他类型的语言中也有过,比如 python 中就有元组的概念) 帮助我们实现了这个需求,std::tuple 可以存放任意类型任意个数的元素。
1.2 tuple 原理
如果让我们来设计元组 std::tuple 的话,底层究竟用什么结构呢?我们可以先复习一下其他一些容器的底层结构:
|-------------|--------|
| 容器 | 底层结构 |
| vector | 数组 |
| list | 链表 |
| map | 红黑树 |
| unorder_map | hash 表 |
上述表格中的底层结构都用不了,因为这些底层结构的所有元素类型都是需要一致的。
那让我们抛开底层结构的固化思维,来考虑一下用本章节介绍的 可变模板参数类来实现吧。
定义变模板参数类,将模板参数包拆分为 第一个模板参数与剩下的模板参数包,在当前类中定义 第一个模板参数的类成员,并在该类构造函数中给该类成员赋值。改了继承一个基类,基类中使用剩下的模板参数包,其余同派生类是相同的。最后,定义一个递归终止的类,递归终止类中无任何模板参数,里面的实现也是空的。
1.3 tuple 源码
cpp
template<std::size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, false>
{
constexpr _Head_base()
: _M_head_impl() { }
constexpr _Head_base(const _Head& __h)
: _M_head_impl(__h) { }
constexpr _Head_base(const _Head_base&) = default;
constexpr _Head_base(_Head_base&&) = default;
static constexpr _Head&
_M_head(_Head_base& __b) noexcept { return __b._M_head_impl; }
static constexpr const _Head&
_M_head(const _Head_base& __b) noexcept { return __b._M_head_impl; }
...
...
...
_Head _M_head_impl;
};
// Basis case of inheritance recursion.
template<std::size_t _Idx, typename _Head>
struct _Tuple_impl<_Idx, _Head>
: private _Head_base<_Idx, _Head>
{
template<std::size_t, typename...> friend class _Tuple_impl;
typedef _Head_base<_Idx, _Head> _Base;
static constexpr _Head&
_M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }
static constexpr const _Head&
_M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }
constexpr _Tuple_impl()
: _Base() { }
explicit
constexpr _Tuple_impl(const _Head& __head)
: _Base(__head) { }
...
...
...
};
/**
* Contains the actual implementation of the @c tuple template, stored
* as a recursive inheritance hierarchy from the first element (most
* derived class) to the last (least derived class). The @c Idx
* parameter gives the 0-based index of the element stored at this
* point in the hierarchy; we use it to implement a constant-time
* get() operation.
*/
template<std::size_t _Idx, typename... _Elements>
struct _Tuple_impl;
/**
* Recursive tuple implementation. Here we store the @c Head element
* and derive from a @c Tuple_impl containing the remaining elements
* (which contains the @c Tail).
*/
template<std::size_t _Idx, typename _Head, typename... _Tail>
struct _Tuple_impl<_Idx, _Head, _Tail...>
: public _Tuple_impl<_Idx + 1, _Tail...>,
private _Head_base<_Idx, _Head>
{
template<std::size_t, typename...> friend class _Tuple_impl;
typedef _Tuple_impl<_Idx + 1, _Tail...> _Inherited;
typedef _Head_base<_Idx, _Head> _Base;
static constexpr _Head&
_M_head(_Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }
static constexpr const _Head&
_M_head(const _Tuple_impl& __t) noexcept { return _Base::_M_head(__t); }
static constexpr _Inherited&
_M_tail(_Tuple_impl& __t) noexcept { return __t; }
static constexpr const _Inherited&
_M_tail(const _Tuple_impl& __t) noexcept { return __t; }
constexpr _Tuple_impl()
: _Inherited(), _Base() { }
explicit
constexpr _Tuple_impl(const _Head& __head, const _Tail&... __tail)
: _Inherited(__tail...), _Base(__head) { }
...
...
...
};
/// Primary class template, tuple
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>
{
typedef _Tuple_impl<0, _Elements...> _Inherited;
constexpr tuple()
: _Inherited() { }
...
...
...
constexpr tuple(const _Elements&... __elements)
: _Inherited(__elements...) { }
...
...
...
};
源码解析:
- struct tuple 是 c++ 中对外提供的元组类,通过源码可以看出,class tuple 是一个可变模板参数类,其模板参数可以是多个类型不同的参数,该类继承自 class _Tuple_impl
cpp
template<typename... _Elements>
class tuple : public _Tuple_impl<0, _Elements...>;
- struct _Tuple_impl 有三个模板参数,_Idx 表示元组中当前元素的下标,从 0 开始,_Head 表示当前存储的元素类型, Tail 表示剩下的模板参数包。该类继承了两个基类,分别是 class _Tuple_impl(就算该类本身,但是其模板参数是不同的),该类不存储元素, _Head_base 是用来存储元素的。
cpp
template<std::size_t _Idx, typename _Head, typename... _Tail>
struct _Tuple_impl<_Idx, _Head, _Tail...>
: public _Tuple_impl<_Idx + 1, _Tail...>,
private _Head_base<_Idx, _Head>;
- 与递归函数需要终止条件一样,递归类的继承也需要终止类,下面就算递归终止类,在该类中,模板参数有两个 _Idx 表示当前元素的下标,_Head 表示元组中最后一个元素的类型,该类不存储元素,是通过继承的 _Head_base 来实现元素的存储的
cpp
template<std::size_t _Idx, typename _Head>
struct _Tuple_impl<_Idx, _Head>
: private _Head_base<_Idx, _Head>;
- struct _Head_base 是 struct _Tuple_impl 的基类,该类又两个模板参数:_Idx 表示元素的下标,_Head 表示当前存储元素的类型,该类的主要作用是在内存中开辟一块内存空间,用来存储成员变量 _Head _M_head_impl;
cpp
template<std::size_t _Idx, typename _Head>
struct _Head_base<_Idx, _Head, false>;
make_tuple 函数 可以帮用户在使用中只传入参数,来构造一个 tuple, 其源码如下:
cpp
template<typename... _Elements>
constexpr tuple<typename __decay_and_strip<_Elements>::__type...>
make_tuple(_Elements&&... __args)
{
typedef tuple<typename __decay_and_strip<_Elements>::__type...>
__result_type;
return __result_type(std::forward<_Elements>(__args)...);
}
通过 __decay_and_strip 将模板参数类型获取出来,定义返回类型 __result_type ,实际就是 tuple 类型,再利用万能转发 std::forward 将参数转发给返回 类型 __result_type ,构造一个临时对象 return 。函数的返回类型用 constexpr 修饰,表示编译器就将该函数执行创造出 tuple 对象。
上面大多数代码都能看懂,__decay_and_strip 有点奇怪,源码如下:
cpp
template<typename _Tp>
struct __strip_reference_wrapper<reference_wrapper<_Tp> >
{
typedef _Tp& __type;
};
template<typename _Tp>
struct __decay_and_strip
{
typedef typename __strip_reference_wrapper<
typename decay<_Tp>::type>::__type __type;
};
/// decay
template<typename _Tp>
class decay
{
typedef typename remove_reference<_Tp>::type __remove_type;
public:
typedef typename __decay_selector<__remove_type>::__type type;
};
通过源码可以看出,__decay_and_strip 就是将模板参数的类型萃取出来,如果模板参数是引用类型,该函数也会将 引用类型去除掉。
1.4 tuple 使用
cpp
#include<iostream>
#include<tuple>
int main()
{
// 1. 直接构造 tuple 对象
std::tuple<float, int, double, long, char, std::string> tup(1.2, 33, 2.3, 66, 'a', "abcde");
std::cout << std::get<0>(tup) << " ";
std::cout << std::get<1>(tup) << " ";
std::cout << std::get<2>(tup) << " ";
std::cout << std::get<3>(tup) << " ";
std::cout << std::get<4>(tup) << " ";
std::cout << std::get<5>(tup) << std::endl;
// 2. 利用 make_tuple 构造 tuple 对象
std::tuple<float, int, double, long, char, std::string> tp = std::make_tuple(1.2, 33, 2.3, 66, 'a', "abcde");
std::cout << std::get<0>(tp) << " ";
std::cout << std::get<1>(tp) << " ";
std::cout << std::get<2>(tp) << " ";
std::cout << std::get<3>(tp) << " ";
std::cout << std::get<4>(tp) << " ";
std::cout << std::get<5>(tp) << std::endl;
return 0;
}
输出:
三 实现简单的 tuple
cpp
// my_tuple.h
template<size_t _Idx, typename ...Args>
struct my_tuple_impl;
template<size_t _Idx,typename _Head, typename ...Args>
struct my_tuple_impl<_Idx, _Head, Args...> : private my_tuple_impl<_Idx+1, Args...>
{
typedef my_tuple_impl<_Idx+1,Args...> _inheritd;
public:
my_tuple_impl(const _Head& head, const Args&... args):m_head(head),_inheritd(args...)
{
}
_Head& head()
{
return m_head;
}
_inheritd& tail()
{
return *this;
}
private:
_Head m_head;
};
template<size_t _Idx,typename _Head>
struct my_tuple_impl< _Idx,_Head>
{
public:
my_tuple_impl(const _Head& head):m_head(head)
{
}
const _Head& head() const
{
return m_head;
}
private:
_Head m_head;
};
template< typename ... Args>
struct my_tuple : public my_tuple_impl<0, Args...>{
typedef my_tuple_impl<0, Args...> impl;
public:
my_tuple(const Args&... args):impl(args...)
{
}
};
// main.cpp
#include<iostream>
#include"my_tuple.h"
int main()
{
my_tuple<float, int, double, long, char, std::string> tuple(1.2, 33, 2.3, 66, 'a', "abcde");
std::cout << tuple.head() << " ";
std::cout << tuple.tail().head() << " ";
std::cout << tuple.tail().tail().head() << " ";
std::cout << tuple.tail().tail().tail().head() << " ";
std::cout << tuple.tail().tail().tail().tail().head() << " ";
std::cout << tuple.tail().tail().tail().tail().tail().head() << " ";
return 0;
}
输出: