Namespaces
Variants

Pack (since C++11)

From cppreference.net
C++ language
General topics
Flow control
Conditional execution statements
Iteration statements (loops)
Jump statements
Functions
Function declaration
Lambda function expression
inline specifier
Dynamic exception specifications ( until C++17* )
noexcept specifier (C++11)
Exceptions
Namespaces
Types
Specifiers
constexpr (C++11)
consteval (C++20)
constinit (C++20)
Storage duration specifiers
Initialization
Expressions
Alternative representations
Literals
Boolean - Integer - Floating-point
Character - String - nullptr (C++11)
User-defined (C++11)
Utilities
Attributes (C++11)
Types
typedef declaration
Type alias declaration (C++11)
Casts
Memory allocation
Classes
Class-specific function properties
Special member functions
Templates
Template specialization
Parameter packs (C++11)
Miscellaneous

팩은 다음 중 하나를 정의하는 C++ 엔터티입니다:

  • 매개변수 팩
  • template parameter pack
  • function parameter pack
(C++20부터)
(C++26부터)

템플릿 매개변수 팩은 0개 이상의 템플릿 인수(상수, 타입, 또는 템플릿)를 받아들이는 템플릿 매개변수입니다. 함수 매개변수 팩은 0개 이상의 함수 인수를 받아들이는 함수 매개변수입니다.

람다 init-capture 팩은 해당 초기화자의 팩 확장 내 각 요소에 대해 init-capture를 도입하는 람다 캡처입니다.

(since C++20)

구조화된 바인딩 팩은 구조화된 바인딩 선언에서 0개 이상의 구조화된 바인딩을 도입하는 식별자입니다.

(since C++26)

패킷의 요소 개수는 다음과 같습니다:

  • 매개변수 팩에 제공된 인수의 개수(팩이 템플릿 또는 함수 매개변수 팩인 경우),
  • 이니셜라이저의 팩 확장에 있는 요소의 개수(팩이 람다 init-capture 팩인 경우),
(C++20부터)
  • 구조적 바인딩 초기화자의 크기에서 구조적 바인딩 선언 내 비 팩(non-pack) 요소의 수를 뺀 값, 단 팩이 구조적 바인딩 팩인 경우.
(C++26부터)

최소 하나의 매개변수 팩을 가진 템플릿을 가변 템플릿 이라고 합니다.

목차

구문

템플릿 매개변수 팩 ( alias template , class template , variable template (C++14부터) , concept (C++20부터) function template 매개변수 목록에 나타남)

type ... pack-name  (선택 사항) (1)
typename | class ... pack-name  (선택 사항) (2)
type-constraint ... pack-name  (선택 사항) (3) (C++20부터)
template < parameter-list > class ... pack-name  (선택 사항) (4) (C++17까지)
template < parameter-list > typename | class ... pack-name  (선택 사항) (4) (C++17부터)

함수 매개변수 팩 (일종의 선언자 , 가변 함수 템플릿의 함수 매개변수 목록에 나타남)

pack-name ... pack-param-name  (선택 사항) (5)

비파라미터 팩의 구문에 대해서는 람다 초기화 캡처 팩 구조화된 바인딩 팩 (C++26부터) 을 참조하십시오.

(C++20부터)

팩 확장 (템플릿 본문 내에서 나타남)

pattern ... (6)
1) 선택적 이름을 가진 상수 템플릿 매개변수 팩
2) 선택적 이름을 가진 타입 템플릿 매개변수 팩
3) 선택적 이름을 가진 제약된 타입 템플릿 매개변수 팩
(C++20부터)
4) 선택적 이름을 가진 템플릿 템플릿 매개변수 팩
5) 선택적 이름을 가진 함수 매개변수 팩
6) 팩 확장: 0개 이상의 pattern 목록으로 확장됩니다. 패턴은 적어도 하나의 팩을 포함해야 합니다.

설명

가변 인자 클래스 템플릿은 임의의 개수의 템플릿 인자로 인스턴스화될 수 있습니다:

template<class... Types>
struct Tuple {};
Tuple<> t0;           // Types에 인수가 없음
Tuple<int> t1;        // Types에 하나의 인수 포함: int
Tuple<int, float> t2; // Types에 두 개의 인수 포함: int와 float
Tuple<0> t3;          // 오류: 0은 타입이 아님

가변 인자 함수 템플릿은 임의의 개수의 함수 인자로 호출될 수 있습니다 (템플릿 인자는 template argument deduction 을 통해 추론됩니다):

template<class... Types>
void f(Types... args);
f();       // 정상: args에 인수가 없음
f(1);      // 정상: args에 하나의 인수 포함: int
f(2, 1.0); // 정상: args에 두 개의 인수 포함: int와 double

기본 클래스 템플릿에서 템플릿 매개변수 팩은 반드시 템플릿 매개변수 목록의 마지막 매개변수여야 합니다. 함수 템플릿에서는, 뒤따르는 모든 매개변수들이 함수 인수로부터 추론 가능하거나 기본 인수를 갖는 경우, 템플릿 매개변수 팩이 목록에서 더 앞쪽에 나타날 수 있습니다:

template<typename U, typename... Ts>    // OK: U 추론 가능
struct valid;
// template<typename... Ts, typename U> // 오류: Ts...가 끝에 위치하지 않음
// struct Invalid;
template<typename... Ts, typename U, typename=void>
void valid(U, Ts...);    // OK: U 추론 가능
// void valid(Ts..., U); // 사용 불가: 이 위치에서 Ts...는 비추론 문맥
valid(1.0, 1, 2, 3);     // OK: U를 double로, Ts를 {int, int, int}로 추론

가변 템플릿의 모든 유효한 특수화가 빈 템플릿 매개변수 팩을 요구하는 경우, 프로그램은 잘못된 형식이며 진단이 필요하지 않습니다.

팩 확장

생략 부호 뒤에 오는 패턴에서, 적어도 하나의 팩 이름이 최소 한 번 이상 나타나는 경우, 그 패턴은 확장되어 팩의 이름이 팩에 있는 각 요소로 순서대로 대체된 패턴 인스턴스 0개 이상으로 변환됩니다. alignment specifiers 의 인스턴스화는 공백으로 구분되며, 다른 인스턴스화는 쉼표로 구분됩니다.

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... pargs는 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)을 동시에 확장합니다
    // (Args and args) simultaneously
    f(h(args...) + args...); // 중첩된 팩 확장:
    // 내부 팩 확장은 "args..."이며, 먼저 확장됩니다
    // 외부 팩 확장은 h(E1, E2, E3) + args...이며, 두 번째로 확장됩니다
    // second (as h(E1, E2, E3) + E1, h(E1, E2, E3) + E2, h(E1, E2, E3) + E3)
}

팩의 요소 개수가 0인 경우(빈 팩), 팩 확장의 인스턴스화는 둘러싼 구문의 구문적 해석을 변경하지 않습니다. 팩 확장을 완전히 생략했을 때 잘못된 형식이 되거나 구문 모호성이 발생하는 경우에도 마찬가지입니다. 인스턴스화는 빈 목록을 생성합니다.

template<class... Bases> 
struct X : Bases... { };
template<class... Args> 
void f(Args... args) 
{
    X<Args...> x(args...);
}
template void f<>(); // OK, X<>는 기본 클래스를 가지지 않음
                     // x는 값 초기화된 X<> 타입의 변수임

확장 궤적

확장이 발생하는 위치에 따라, 결과적으로 생성되는 쉼표로 구분된(또는 alignment specifiers 의 경우 공백으로 구분된) 목록은 서로 다른 종류의 목록입니다: 함수 매개변수 목록, 멤버 초기화 목록, 속성 목록 등. 다음은 모든 허용되는 컨텍스트 목록입니다:

함수 인수 목록

팩 확장은 함수 호출 연산자의 괄호 안에 나타날 수 있으며, 이 경우 생략표 왼쪽의 가장 큰 표현식 또는 brace-enclosed initializer list 가 확장되는 패턴입니다:

f(args...);              // f(E1, E2, 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)

괄호 초기화자

팩 확장은 직접 초기화(direct initialization) 의 괄호 안, 함수 방식 캐스트(function-style cast) , 그리고 다른 상황들( 멤버 초기화(member initializer) , new-expression 등)에서 나타날 수 있으며, 이 경우 규칙은 위의 함수 호출 표현식에 대한 규칙과 동일합니다:

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 issue 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)... {}
};

람다 캡처

팩 확장은 람다 표현식의 캡처 절에 나타날 수 있습니다:

template<class... Args>
void f(Args... args)
{
    auto lm = [&, args...] { return g(args...); };
    lm();
}

sizeof... 연산자

sizeof... 연산자 역시 팩 확장(pack expansion)으로 분류됩니다:

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; // 확장 후 정렬 지정자는
                     // alignas(int) alignas(short) 입니다
                     // (사이에 쉼표 없음)

속성 목록

팩 확장은 속성의 명세에서 허용하는 경우 attributes 목록에서 허용됩니다. 예를 들어:

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부터)


Pack 인덱싱

Pack 인덱싱 에서 pack 확장은 확장되지 않은 pack 뒤에 생략문과 첨자가 옵니다. Pack 인덱싱 표현식의 패턴은 identifier 이고, pack 인덱싱 지정자의 패턴은 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>>

Fold 확장 제약조건

Fold 확장 제약조건 에서 패턴은 해당 fold 확장 제약조건의 제약조건입니다.

Fold 확장 제약조건은 인스턴스화되지 않습니다.

(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) // 기본 함수
{
    std::cout << format;
}
template<typename T, typename... Targs>
void tprintf(const char* format, T value, Targs... Fargs) // 재귀 가변 함수
{
    for (; *format != '\0'; format++)
    {
        if (*format == '%')
        {
            std::cout << value;
            tprintf(format + 1, Fargs...); // 재귀 호출
            return;
        }
        std::cout << *format;
    }
}
int main()
{
    tprintf("% world% %\n", "Hello", '!', 123);
}

출력:

Hello world! 123

결함 보고서

다음의 동작 변경 결함 보고서들은 이전에 발표된 C++ 표준에 소급 적용되었습니다.

DR 적용 대상 게시된 동작 올바른 동작
CWG 1533 C++11 멤버에 대한 멤버 초기화자에서 팩 확장이 발생할 수 있었음 허용되지 않음
CWG 2717 C++11 정렬 지정자의 인스턴스화가 쉼표로 구분되었음 공백으로 구분됨

참고 항목

Function template 함수 패밀리를 정의함
Class template 클래스 패밀리를 정의함
sizeof... 패킹의 요소 개수를 조회함
C-style variadic function 가변 개수의 인수를 받음
Preprocessor macros 마찬가지로 가변적일 수 있음
Fold expression 이항 연산자를 통해 패킹을 축약함
Pack indexing 패킹의 지정된 인덱스 요소에 접근함