包 (自 C++11 起)
包是定義以下內容之一的 C++ 實體
- 引數包
- 模板引數包
- 函式引數包
(C++20 起) |
(C++26 起) |
模板引數包是接受零個或多個模板實參(非型別、型別或模板)的模板引數。函式引數包是接受零個或多個函式實參的函式引數。
lambda 初始化捕獲包是一種 lambda 捕獲,它為其初始化器中包展開的每個元素引入一個初始化捕獲。 |
(C++20 起) |
結構化繫結包是結構化繫結宣告中的一個識別符號,它引入零個或多個結構化繫結。 |
(C++26 起) |
包的元素數量等於
- 如果包是模板或函式引數包,則為引數包提供的實引數量,
|
(C++20 起) |
|
(C++26 起) |
具有至少一個引數包的模板稱為 可變引數模板。
目錄 |
[編輯] 語法
模板引數包(出現在別名模板、類模板、變數模板(C++14 起)、概念(C++20 起) 和函式模板引數列表中)
型別 ... 包名 (可選) |
(1) | ||||||||
typename | class ... 包名 (可選) |
(2) | ||||||||
型別約束 ... 包名 (可選) |
(3) | (C++20 起) | |||||||
template < 引數列表 > class ... 包名 (可選) |
(4) | (C++17 前) | |||||||
template < 引數列表 > typename | class ... 包名 (可選) |
(4) | (C++17 起) | |||||||
函式引數包(宣告符的一種形式,出現在可變引數函式模板的函式引數列表中)
包名 ... 包引數名 (可選) |
(5) | ||||||||
有關非引數包的語法,請參見lambda 初始化捕獲包 和結構化繫結包(C++26 起)。 |
(C++20 起) |
包展開(出現在模板的主體中)
模式 ... |
(6) | ||||||||
3) 帶有可選名稱的受約束型別模板引數包 |
(C++20 起) |
pattern
列表。模式必須至少包含一個包。[編輯] 解釋
可變引數類模板可以使用任意數量的模板實參進行例項化
template<class... Types> struct Tuple {}; Tuple<> t0; // Types contains no arguments Tuple<int> t1; // Types contains one argument: int Tuple<int, float> t2; // Types contains two arguments: int and float Tuple<0> t3; // error: 0 is not a type
可變引數函式模板可以使用任意數量的函式實參進行呼叫(模板實參透過模板實參推導推匯出)
template<class... Types> void f(Types... args); f(); // OK: args contains no arguments f(1); // OK: args contains one argument: int f(2, 1.0); // OK: args contains two arguments: int and double
在主類模板中,模板引數包必須是模板引數列表中的最後一個引數。在函式模板中,模板引數包可以出現在列表中的更早位置,前提是所有後續引數都可以從函式實參中推匯出來,或者具有預設實參
template<typename U, typename... Ts> // OK: can deduce U struct valid; // template<typename... Ts, typename U> // Error: Ts... not at the end // struct Invalid; template<typename... Ts, typename U, typename=void> void valid(U, Ts...); // OK: can deduce U // void valid(Ts..., U); // Can't be used: Ts... is a non-deduced context in this position valid(1.0, 1, 2, 3); // OK: deduces U as double, Ts as {int, int, int}
如果可變引數模板的每個有效特化都需要一個空的模板引數包,則程式格式錯誤,不需要診斷。
[編輯] 包展開
在模式後跟省略號,其中至少一個包的名稱至少出現一次,則會展開為模式的零個或多個例項化,其中包的名稱按順序被包中的每個元素替換。 對齊說明符的例項化以空格分隔,其他例項化以逗號分隔。
template<class... Us> void f(Us... pargs) {} template<class... Ts> void g(Ts... args) { f(&args...); // “&args...” is a pack expansion // “&args” is its pattern } g(1, 0.2, "a"); // Ts... args expand to int E1, double E2, const char* E3 // &args... expands to &E1, &E2, &E3 // Us... pargs expand to 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>... is the pack expansion // Pair<Args1, Args2> is the pattern }; }; typedef zip<short, int>::with<unsigned short, unsigned>::type T1; // Pair<Args1, Args2>... expands to // Pair<short, unsigned short>, Pair<int, unsigned int> // T1 is Tuple<Pair<short, unsigned short>, Pair<int, unsigned>> // typedef zip<short>::with<unsigned short, unsigned>::type T2; // error: pack expansion contains packs of different lengths
如果一個包展開巢狀在另一個包展開中,則內部包展開中出現的包會由它展開,並且外部包展開中必須提及另一個包,但內部包展開中沒有
template<class... Args> void g(Args... args) { f(const_cast<const Args*>(&args)...); // const_cast<const Args*>(&args) is the pattern, it expands two packs // (Args and args) simultaneously f(h(args...) + args...); // Nested pack expansion: // inner pack expansion is "args...", it is expanded first // outer pack expansion is h(E1, E2, E3) + args..., it is expanded // second (as h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3) }
當包中的元素數量為零(空包)時,包展開的例項化不會改變外部構造的語法解釋,即使在完全省略包展開會導致格式錯誤或導致語法歧義的情況下也是如此。例項化會生成一個空列表。
template<class... Bases> struct X : Bases... { }; template<class... Args> void f(Args... args) { X<Args...> x(args...); } template void f<>(); // OK, X<> has no base classes // x is a variable of type X<> that is value-initialized
[編輯] 展開位置
根據展開發生的位置,生成的逗號分隔(或對齊說明符的空格分隔)列表是不同型別的列表:函式引數列表、成員初始化器列表、屬性列表等。以下是所有允許的上下文列表
[編輯] 函式實參列表
包展開可以出現在函式呼叫運算子的括號內,在這種情況下,省略號左側的最大表達式或花括號括起來的初始化器列表是展開的模式
f(args...); // expands to f(E1, E2, E3) f(&args...); // expands to f(&E1, &E2, &E3) f(n, ++args...); // expands to f(n, ++E1, ++E2, ++E3); f(++args..., n); // expands to 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...); // expands to // f(h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
[編輯] 帶括號的初始化器
包展開可以出現在直接初始化器、函式式轉換和其他上下文(成員初始化器、new 表示式等)的括號內,在這種情況下,規則與上述函式呼叫表示式的規則相同
Class c1(&args...); // calls Class::Class(&E1, &E2, &E3) Class c2 = Class(n, ++args...); // calls 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}; // since initializer lists guarantee sequencing, this can be used to // call a function on each element of a pack, in order: 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; // expands to container<A, B, E1, E2, E3> container<C..., A, B> t2; // expands to container<E1, E2, E3, A, B> container<A, C..., B> t3; // expands to container<A, E1, E2, E3, B> }
[編輯] 函式引數列表
在函式引數列表中,如果省略號出現在引數宣告中(無論它是否命名函式引數包(如 Args...
args)),則引數宣告是模式
template<typename... Ts> void f(Ts...) {} f('a', 1); // Ts... expands to void f(char, int) f(0.1); // Ts... expands to 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] expands to // const char (&)[2], int(&)[1]
注意:在模式 Ts (&...arr)[N]
中,省略號是最內部的元素,而不是像所有其他包展開那樣是最後一個元素。
注意:不允許 Ts (&...)[N]
,因為 C++11 語法要求帶括號的省略號有一個名稱:CWG issue 1488。
[編輯] 模板引數列表
包展開可以出現在模板引數列表中
template<typename... T> struct value_holder { template<T... Values> // expands to a non-type template parameter struct apply {}; // list, such as <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...) { // ... throw different Xs in different situations } |
(C++17 前) |
[編輯] 對齊說明符
包展開允許出現在關鍵字alignas
使用的型別列表和表示式列表中。例項化以空格分隔
template<class... T> struct Align { alignas(T...) unsigned char buffer[128]; }; Align<int, short> a; // the alignment specifiers after expansion are // alignas(int) alignas(short) // (no comma in between)
[編輯] 屬性列表
如果屬性規範允許,包展開允許出現在屬性列表中。例如
template<int... args> [[vendor::attr(args)...]] void* f();
摺疊表示式在摺疊表示式中,模式是整個不包含未展開包的子表示式。 Using 宣告在using 宣告中,省略號可以出現在宣告符列表中,這在從模板引數包派生時很有用 template<typename... bases> struct X : bases... { using bases::g...; }; X<B, D> x; // OK: B::g and D::g introduced |
(C++17 起) |
包索引在包索引中,包展開包含一個未展開的包,後跟省略號和下標。包索引表示式的模式是識別符號,而包索引說明符的模式是typedef-name。 consteval auto first_plus_last(auto... args) { return args...[0] + args...[sizeof...(args) - 1]; } static_assert(first_plus_last(5) == 10); static_assert(first_plus_last(5, 4) == 9); static_assert(first_plus_last(5, 6, 2) == 7); Friend 宣告在類friend 宣告中,每個型別說明符後都可以跟一個省略號 struct C {}; struct E { struct Nested; }; template<class... Ts> class R { friend Ts...; }; template<class... Ts, class... Us> class R<R<Ts...>, R<Us...>> { friend Ts::Nested..., Us...; }; R<C, E> rce; // classes C and E are friends of R<C, E> R<R<E>, R<C, int>> rr; // E::Nested and C are friends of R<R<E>, R<C, int>> 摺疊展開的約束在摺疊展開的約束中,模式是該摺疊展開的約束的約束。 摺疊展開的約束未例項化。 |
(C++26 起) |
[編輯] 注意
本節不完整 原因:關於部分特化和其他訪問單個元素的方法的幾句話?提及遞迴與對數與摺疊表示式等快捷方式 |
特性測試宏 | 值 | 標準 | 特性 |
---|---|---|---|
__cpp_variadic_templates |
200704L |
(C++11) | 可變引數模板 |
__cpp_pack_indexing |
202311L |
(C++26) | 包索引 |
[編輯] 示例
以下示例定義了一個類似於 std::printf 的函式,它將格式字串中出現的每個字元 %
替換為一個值。
當只傳遞格式字串且沒有引數展開時,呼叫第一個過載。
第二個過載包含一個單獨的模板引數用於引數的頭部和一個引數包,這允許遞迴呼叫只傳遞引數的尾部,直到它變空。
Targs
是模板引數包,Fargs
是函式引數包。
#include <iostream> void tprintf(const char* format) // base function { std::cout << format; } template<typename T, typename... Targs> void tprintf(const char* format, T value, Targs... Fargs) // recursive variadic function { for (; *format != '\0'; format++) { if (*format == '%') { std::cout << value; tprintf(format + 1, Fargs...); // recursive call return; } std::cout << *format; } } int main() { tprintf("% world% %\n", "Hello", '!', 123); }
輸出
Hello world! 123
[編輯] 缺陷報告
下列更改行為的缺陷報告追溯地應用於以前出版的 C++ 標準。
缺陷報告 | 應用於 | 釋出時的行為 | 正確的行為 |
---|---|---|---|
CWG 1533 | C++11 | 包展開可以出現在成員的成員初始化器中 | 不允許 |
CWG 2717 | C++11 | 對齊說明符的例項化以逗號分隔 | 它們以空格分隔 |
[編輯] 另請參閱
函式模板 | 定義函式族 |
類模板 | 定義類族 |
sizeof...
|
查詢包中的元素數量 |
C 風格可變引數函式 | 接受可變數量的引數 |
預處理器宏 | 也可以是可變引數的 |
摺疊表示式 | 透過二元運算子約化一個包 |
包索引 | 訪問包中指定索引處的元素 |