形参包分两种:
- 模板形参包
- 函数模板形参包
模板形参包
出现于别名模版、类模板、变量模板及函数模板形参列表中,简单说就是跟在template关键字后面尖括号<>里的东西
语法:
-
类型 ...
Args(可选) (1) -
typename
|
class
...
Args(可选) (2) -
template
<
形参列表>
typename
(C++17)|
class
...
Args(可选) (3)
(1)带可选名字的非类型模板形参包。如template <std::size_t... ty> struct Tuple{}; 表示存在多个std::size_t类型的参数:
(2)带可选名字的类型模板形参包。如template <typename... T> struct Tuple{}; 表示存在多个T类型(int, long, double等)的参数: std::tuple<int, long, double>
(3)带可选名字的模板模板形参包。如template <typename T, typename U, typename...V> struct Tuple{}; 表示有T,U以及多种V类型数据
template<class ... Types> struct Tuple {};
Tuple<> t0; // Types 不包含实参
Tuple<int> t1; // Types 包含一个实参:int
Tuple<int, float> t2; // Types 包含二个实参:int 与 float
Tuple<0> error; // 错误:0 不是类型
template<typename U, typename... Ts> struct Valid; // Ts必须时最后一个
函数模板形参包
出现于变参函数模板的函数形参列表中,简单说就是存在于函数参数里
语法:
-
Args ...
args(可选) (1)
(1)带可选名字的函数形参包。
template<class ... Types> void f(Types ... args);
f(); // OK:args 不包含实参
f(1); // OK:args 包含一个实参:int
f(2, 1.0); // OK:args 包含二个实参:int 与 double
template<typename ...Ts, typename U, typename=void>
void valid(U, Ts...); // 函数模板中 对Ts顺序无要求,只要实际能推导出即可
形参包展开
出现于变参模板体中
语法:
-
模式 ... (1)
(1)形参包展开:展开成零或更多 模式 的逗号分隔列表。模式必须包含至少一个形参包。如func( args...);
模式后随省略号,其中至少有一个形参包的名字至少出现一次,其被展开成零或更多个逗号分隔的模式实例,
其中形参包的名字按顺序被替换成包中的各个元素
template<class ...Us> void f(Us... pargs) {}
template<class ...Ts> void g(Ts... args) {
f(&args...); // “&args...” 是包展开
// “&args” 是其模式
}
g(1, 0.2, "a"); // Ts... args 展开成 int E1, double E2, const char* E3
// &args... 展开成 &E1, &E2, &E3
// Us... 展开成 int* E1, double* E2, const char** E3
若两个形参包出现于同一模式中,则它们同时展开,而且它们必须有相同长度:
template<typename...> struct Tuple {};
template<typename T1, typename T2> struct Pair {};
template<class ...Args1> struct zip {
template<class ...Args2> struct with {
typedef Tuple<Pair<Args1, Args2>...> type;
// Pair<Args1, Args2>... 是包展开
// Pair<Args1, Args2> 是模式
};
};
typedef zip<short, int>::with<unsigned short, unsigned>::type T1;
// Pair<Args1, Args2>... 展开成
// Pair<short, unsigned short>, Pair<int, unsigned int>
// T1 是 Tuple<Pair<short, unsigned short>, Pair<int, unsigned>>
typedef zip<short>::with<unsigned short, unsigned>::type T2;
// 错误:包展开中包含不同长度的形参包
若包展开内嵌于另一包展开中,则其所展开的是出现于最内层包展开的形参包,并且必须在外围(而非最内层)
的包展开中必须提及另一个包:
template<class ...Args>
void g(Args... args) {
f(const_cast<const Args*>(&args)...);
// const_cast<const Args*>(&args) 是模式,它同时展开两个包(Args 与 args)
f(h(args...) + args...); // 嵌套包展开:
// 内层包展开是“args...”,它首先展开
// 外层包展开是 h(E1, E2, E3) + args 它被第二次展开
// (成为 h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
}
展开场所
取决于发生展开的场所,其所产生的逗号分隔列表可以是不同种类的列表:函数形参列表,成员初始化器列表,属性列表,等等。以下列出了所有允许的语境。
函数实参列表
包展开可以出现在函数调用运算符的括号内,此情况下省略号左侧的最大表达式或花括号初始化器列表是被展开的模式。
f(&args...); // 展开成 f(&E1, &E2, &E3)
f(n, ++args...); // 展开成 f(n, ++E1, ++E2, ++E3);
f(++args..., n); // 展开成 f(++E1, ++E2, ++E3, n);
f(const_cast<const Args*>(&args)...);
// f(const_cast<const E1*>(&X1), const_cast<const E2*>(&X2), const_cast<const E3*>(&X3))
f(h(args...) + args...); // 展开成
// f(h(E1,E2,E3) + E1, h(E1,E2,E3) + E2, h(E1,E2,E3) + E3)
正式而言,函数调用表达式中的表达式列表被归类为初始化器列表,其模式是初始化器子句,它或是赋值表达式,或是花括号初始化器列表。
有括号初始化器
包展开可出现于直接初始化器,函数式转型及其他语境(成员初始化器,new 表达式等)的括号之内,这种情况下的规则与适用于上述函数调用表达式的规则相同:
Class c1(&args...); // 调用 Class::Class(&E1, &E2, &E3)
Class c2 = Class(n, ++args...); // 调用 Class::Class(n, ++E1, ++E2, ++E3);
::new((void *)p) U(std::forward<Args>(args)...) // std::allocator::allocate
花括号包围的初始化器
在花括号初始化器列表(花括号包围的初始化器和其他花括号初始化器列表的列表,用于列表初始化和其他一些语境中)中,也可以出现包展开:
template<typename... Ts> void func(Ts... args){
const int size = sizeof...(args) + 2;
int res[size] = {1,args...,2};
// 因为初始化器列表保证顺序,所以这可用于按顺序对包的每个元素调用函数:
int dummy[sizeof...(Ts)] = { (std::cout << args, 0)... };
}
模板实参列表
包展开可用于模板形参列表的任何位置,前提是模板拥有与该展开相匹配的形参。
template<class A, class B, class...C> void func(A arg1, B arg2, C...arg3)
{
container<A,B,C...> t1; // 展开成 container<A,B,E1,E2,E3>
container<C...,A,B> t2; // 展开成 container<E1,E2,E3,A,B>
container<A,C...,B> t3; // 展开成 container<A,E1,E2,E3,B>
}
函数形参列表
在函数形参列表中,若省略号出现于某个形参声明中(无论它是否指名函数形参包(例如在 Args ...
args中)),则该形参声明是模式:
template<typename ...Ts> void f(Ts...) {}
f('a', 1); // Ts... 展开成 void f(char, int)
f(0.1); // Ts... 展开成 void f(double)
template<typename ...Ts, int... N> void g(Ts (&...arr)[N]) {}
int n[1];
g<const char, int>("a", n); // Ts (&...arr)[N] 展开成
// const char (&)[2], int(&)[1]
注意:在模式 Ts (&...arr)[N]
中,省略号是最内层元素,而非如所有其他包展开中一样为其最后元素。
注意:Ts (&...)[N]
不被允许,因为 C++11 语法要求带括号的省略号形参拥有名字:CWG #1488。
模板形参列表
包展开可以出现于模板形参列表中:
template<typename... T> struct value_holder
{
template<T... Values> // 展开成非类型模板形参列表,
struct apply { }; // 例如 <int, char, int(&)[5]>
};
基类说明符与成员初始化器列表
包展开可以用于指定类声明中的基类列表。典型情况下,这也意味着其构造函数也需要在成员初始化器列表中使用包展开,以调用这些基类的构造函数:
template<class... Mixins>
class X : public Mixins... {
public:
X(const Mixins&... mixins) : Mixins(mixins)... { }
};
Lambda 捕获
包展开可以出现于 lambda 表达式的捕获子句中
template<class ...Args>
void f(Args... args) {
auto lm = [&, args...] { return g(args...); };
lm();
}
sizeof... 运算符
sizeof...
也被归类为包展开
template<class... Types>
struct count {
static const std::size_t value = sizeof...(Types);
};
动态异常说明
动态异常说明中的异常列表亦可为包展开
template<class...X> void func(int arg) throw(X...)
{
// ... 在不同情形下抛出不同的 X
}
对齐说明符
包展开允许在关键词 alignas 所用的类型列表和表达式列表中使用
属性列表
包展开允许在属性列表中使用,如 [[attributes...]]。例如:void [[attributes...]] function()
折叠表达式 在折叠表达式中,模式是不包含未展开形参包的整个子表达式。 using 声明 在 using 声明中,省略号可以出现于声明器列表内,这对于从一个形参包进行派生时有用: | (C++17 起) |
template <typename... bases>
struct X : bases... {
using bases::g...;
};
X<B, D> x; // OK:引入 B::g 与 D::g
折叠表达式(C++17 起)
< cpp | language
以二元运算符对形参包进行规约(折叠)。
语法
( 形参包 op ... ) | (1) | ||||||||
( ... op 形参包 ) | (2) | ||||||||
( 形参包 op ... op 初值 ) | (3) | ||||||||
( 初值 op ... op 形参包 ) | (4) | ||||||||
1) 一元右折叠
2) 一元左折叠
3) 二元右折叠
4) 二元左折叠
op | - | 任何下列 32 个二元运算符之一:+ - * / % ^ & | = < > << >> += -= *= /= %= ^= &= |= <<= >>= == != <= >= && || , .* ->*。在二元折叠中,两个 op 必须相同。 |
形参包 | - | 含未展开的形参包且其顶层不含有优先级低于转型(正式而言,是 转型表达式)的运算符的表达式 |
初值 | - | 不含未展开的形参包且其顶层不含有优先级低于转型(正式而言,是 转型表达式)的运算符的表达式 |
注意开与闭括号是折叠表达式的一部分。
解释
折叠表达式的实例化按如下方式展开成表达式 e
:
1) 一元右折叠 (E op ...) 成为 (E1 op (... op (EN-1 op EN)))
2) 一元左折叠 (... op E) 成为 (((E1 op E2) op ...) op EN)
3) 二元右折叠 (E op ... op I) 成为 (E1 op (... op (EN−1 op (EN op I))))
4) 二元左折叠 (I op ... op E) 成为 ((((I op E1) op E2) op ...) op EN)
(其中 N 是包展开中的元素数)
例如,
template<typename... Args>
bool all(Args... args) { return (... && args); }
bool b = all(true, true, true, false);
// 在 all() 中,一元左折叠展开成
// return ((true && true) && true) && false;
// b 为 false
将一元折叠用于零长包展开时,仅允许下列运算符:
1) 逻辑与(&&)。空包的值为 true
2) 逻辑或(||)。空包的值为 false
3) 逗号运算符(,)。空包的值为 void()
注解
若用作 初值 或 形参包 的表达式在顶层具有优先级低于转型的运算符,则它可以加括号:
template<typename ...Args>
int sum(Args&&... args) {
// return (args + ... + 1 * 2); // 错误:优先级低于转型的运算符
return (args + ... + (1 * 2)); // OK
}
示例
#include <iostream>
#include <vector>
#include <climits>
#include <cstdint>
#include <type_traits>
#include <utility>
template<class ...Args>
void printer(Args&&... args) {
(std::cout << ... << args) << '\n';
}
template<class T, class... Args>
void push_back_vec(std::vector<T>& v, Args&&... args)
{
static_assert((std::is_constructible_v<T, Args&> && ...));
(v.push_back(args), ...);
}
// 基于 http://stackoverflow.com/a/36937049 的编译时端序交换
template<class T, std::size_t... N>
constexpr T bswap_impl(T i, std::index_sequence<N...>) {
return (((i >> N*CHAR_BIT & std::uint8_t(-1)) << (sizeof(T)-1-N)*CHAR_BIT) | ...);
}
template<class T, class U = std::make_unsigned_t<T>>
constexpr U bswap(T i) {
return bswap_impl<U>(i, std::make_index_sequence<sizeof(T)>{});
}
int main()
{
printer(1, 2, 3, "abc");
std::vector<int> v;
push_back_vec(v, 6, 2, 45, 12);
push_back_vec(v, 1, 2, 9);
for (int i : v) std::cout << i << ' ';
static_assert(bswap<std::uint16_t>(0x1234u)==0x3412u);
static_assert(bswap<std::uint64_t>(0x0123456789abcdefULL)==0xefcdab8967452301ULL);
}
/*
输出:
123abc
6 2 45 12 1 2 9
*/
简单示例
struct A {
void print() {
cout << "struct A" << endl;
}
};
struct B {
void print() {
cout << "struct B" << endl;
}
};
template <typename... base>
struct X : base... {
using base::print...;
};
void test_X()
{
X<A, B> x;
x.A::print();
x.B::print();
}
template <typename... Args>
bool all(Args...args) {
return (args && ...);
}
/* 折叠表达式 */
void test_Foldedexpression(){
/* all(true, false, true) 展开为 (true && (false && true)) */
cout << std::boolalpha << all(true, false, true) << endl;
}
template <typename T, T... ints>
void print_sequence(std::integer_sequence<T, ints...> int_seq) {
cout << "The sequence size is " << int_seq.size() << ": ";
((cout << ints << ' '), ...);
cout << endl;
}
// std::integer_sequence<std::size_t, index...> 展开为0,1,2,3
template <typename Array, std::size_t...index>
auto arraytotuple_impl(const Array& a, std::integer_sequence<std::size_t, index...>) {
return std::make_tuple(a[index]...); // 展开为a[0],a[1],a[2],a[3]
}
template <typename T, std::size_t N, typename Indics = std::make_index_sequence<N>>
auto arraytotuple(const std::array<T, N>& a) {
return arraytotuple_impl(a, Indics{});
}
// std::index_sequence<Is...> 展开为0,1,2,3的下表序列
template <typename Tuple, std::size_t ...Is>
void print_tuple_impl(const Tuple& t, std::index_sequence<Is...>)
{
((cout << std::get<Is>(t) << ' '), ...);
}
template <typename... Ty>
void print_tuple(const std::tuple<Ty...>& t) {
cout << sizeof...(Ty) << ':'; // 获取参数个数: 4
cout << sizeof(t) << ": "; // 获取t占用字节大小: 16
((cout<< typeid(Ty).name() << ' '), ...); // 输出类型:int int int int
print_tuple_impl(t, std::index_sequence_for<Ty...>{});
cout << endl;
}
void test_sequence() {
print_sequence(std::make_index_sequence<10>{});
// array,tuple都是编译器能确定size的类型
std::array<int, 4> a = { 0, 1, 4, 7 };
auto tuple = arraytotuple(a);
print_tuple(tuple);
}