变参类模板和变参表达式

变参表达式

计算应用

变参表达式不仅仅可以传递所有参数,而且可以进行计算:

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
#include <iostream>

template <typename T>
class AddSpace
{
private:
const T &elem;

public:
AddSpace(const T &elem) : elem(elem) {}
friend std::ostream &operator<<(std::ostream &os, const AddSpace<T> &obj)
{
return os << obj.elem << ' ';
}
};

template <typename... Args>
void print(const Args &&...args)
{
(std::cout << ... << AddSpace(args)) << std::endl;
}

template <typename... Args>
void Doubler(Args &&...args)
{
print((args + args)...);
}

int main()
{
Doubler(1, 2, 3, std::string("hello"));
return 0;
}

模板函数Doubler()给参数包中的每个参数都加上其自身,再将新的到的所有参数打包传给print函数模板。

程序输出:

1
2 4 6 hellohello

通常我们使用移动语义来进行完美转发以防止多余的数据复制,事实上,作用于普通参数的规则仍然对变参适用,如传值则会拷贝实参且类型退化,传引用则会引用实参并且不会出现类型退化。

类型应用

我们可以借助变参模板和traits库来对traits库中的工具进行拓展,如std::is_same<T1, T2>::value用来判断T1,T2类型是否相同,我们可以使用变参模板给它拓展到不限个参数的情况:

1
2
3
4
5
template <typename T, typename... Args>
constexpr bool is_all_same_v(T, Args...)
{
return (std::is_same_v<T, Args> && ...);
}

注:使用std::is_same_v<>等价于std::is_same<>::value

我们将第一个参数分离出来,既可以判断参数包中所有参数类型是否相同,也可以在第一个参数引入一个自定义参数来看参数包中的参数类型是否是某一个指定类型。

变参索引

使用变参索引来访问第一个实参中的元素:

1
2
3
4
5
template <typename T, typename... Index>
void PrintIdx(const T &t, Index... index)
{
print(t[index]...);
}

另一种写法:

1
2
3
4
5
template <std::size_t... Indices, typename T>
void PrintIdx2(const T &t)
{
print(t[Indices]...);
}

使用方法:

1
2
3
4
5
6
7
int main()
{
int a[] = {1, 2, 3, 4, 5};
PrintIdx(a, 0, 1, 2, 3, 4);
PrintIdx2<0, 1, 2, 3, 4>(a);
return 0;
}

变参类模板

一个非常重要的类std::tuple可以存储任意数量类型的参数,我们放到后面讲。

我们可以使用变参来指定某一类中可以存放哪些参数,这些参数间是的关系:

1
2
template <typename... Types>
class Variant;

变参推导指引

在我们对std::array使用初始化参数列表时,并没有指定它的元素个数和类型,对它的元素个数和类型进行推导的过程就是用了变参推到指引

1
2
3
4
template<typename _Tp, typename... _Up>
array(_Tp, _Up...)
-> array<enable_if_t<(is_same_v<_Tp, _Up> && ...), _Tp>,
1 + sizeof...(_Up)>;

这是标准库中对array进行的推导,元素总数是1 + sizeof...(U)is_same_v用来判断参数包中的类型是否相同,若不同,则推到失败,因此构建array成功后所有元素类型必然相同。