template for 확장
template for 확장 (template for expansion, C++26부터)
여러 (아마 서로 다른) 표현식에 같은 논리를 적용하는 문장 여러 개를, 더 읽기 쉬운 한 문장으로 표현하고 싶을 때 쓰는 게 template for예요. 컴파일 타임에 결정되는 언어 구성을 그 요소들로 확장해 줘요. 이 페이지에서는 template for 문의 문법과, 확장 이니셜라이저의 형태에 따라 달라지는 세 가지 범주를 정리할게요.
출처: cppreference
본문
template for는 컴파일 타임에 결정 가능한 언어 구성을 그 요소들로 확장해요.
여러 (아마 이질적인) 표현식에 같은 논리를 적용하는 문장 여러 개보다 읽기 쉬운 동등한 형태로 쓰여요.
문법
attr(선택) template for ( init-statement(선택) item-declaration : expansion-initializer ) compound-statement |
| 항목 | 의미 | |
|---|---|---|
| attr | - | 임의 개수의 속성 |
| init-statement | - | 다음 중 하나 — 표현식 문(널 문 ;일 수도 있음) — 단순 선언 (보통 이니셜라이저가 있는 변수 선언, 임의 개수의 변수나 구조적 바인딩 선언을 선언할 수 있음) — 별명 선언. 어떤 init-statement든 세미콜론으로 끝나야 한다는 점에 유의하세요. 그래서 흔히 비공식적으로 "표현식이나 선언 뒤에 세미콜론이 붙은 것"이라고 표현해요. |
| item-declaration | - | 다음 중 하나 — 다음 제한을 가진 단순 선언: · 선언자가 하나뿐이다. · 선언자가 이니셜라이저를 가지면 안 된다. · 선언 지정자 시퀀스는 타입 지정자와 constexpr만 담을 수 있고, 클래스나 열거를 정의할 수 없다. — 구조적 바인딩 선언 |
| expansion-initializer | - | 다음 중 하나 — 표현식 — 중괄호로 감싸고 (선택적으로 꼬리 쉼표가 있는) 쉼표로 구분된 표현식 목록 |
| compound-statement | - | 임의의 복합 문 |
동작 방식 (Explanation)
루프를 compound-statement 안에서 미리 종료해야 한다면 break 문을 종료 문으로 쓸 수 있어요.
현재 반복을 compound-statement 안에서 미리 종료해야 한다면 continue 문을 단축키로 쓸 수 있어요.
expansion-initializer의 문법과 속성에 따라 template for 문은 다음 세 범주 중 하나로 나뉘어요.
열거 확장 문 (Enumerating expansion statement)
expansion-initializer가 중괄호로 감싼 표현식 목록이면, 그 문은 열거 확장 문이에요.
표현식 목록의 요소 수를 N, 표현식 목록의 I번째 요소(인덱스는 0부터)를 /*expr-I*/라고 할 때, 열거 확장 문은 expansion-initializer의 임시 객체 수명 확장(아래 참고)을 제외하면 다음과 동등해요:
{
init-statement
{
item-declaration = /*expr-0*/;
compound-statement
}
// ...
{
item-declaration = /*expr-(N-1)*/;
compound-statement
}
}
표현식 목록은 팩 확장의 결과일 수 있어요.
반복 확장 문 (Iterating expansion statement)
expansion-initializer가 확장-반복 가능한(expansion-iterable, 아래 참고) 표현식이면, 그 문은 반복 확장 문이에요.
반복 확장 문은 expansion-initializer의 임시 객체 수명 확장(아래 참고)을 제외하면 다음과 동등해요. /* */로 감싼 변수와 표현식은 설명용(오직 exposition)이에요:
constexpr auto /*N*/ = [&] consteval
{
std::ptrdiff_t result = 0;
auto b = /*begin-expr*/;
auto e = /*end-expr*/;
for (; b != e; ++b)
++result;
return result;
}();
{
init-statement
constexpr(선택) decltype(auto) /*range*/ = (expansion-initializer);
constexpr(선택) auto /*begin*/ = /*begin-expr*/;
{
constexpr(선택) /*iter*/ = /*begin*/ + decltype(begin - begin){0};
item-declaration = */*iter*/;
compound-statement
}
// ...
{
constexpr(선택) /*iter*/ = /*begin*/ + decltype(begin - begin){/*N*/ - 1};
item-declaration = */*iter*/;
compound-statement
}
}
/*range*/, /*begin*/, /*iter*/는 item-declaration에 constexpr 지정자가 있는 경우에만 constexpr로 선언돼요.
설명용 표현식 /*begin-expr*/와 /*end-expr*/는 다음과 같이 정의돼요:
/*range*/의 타입이 배열 타입R에 대한 참조이면:R이 경계N이면,/*begin-expr*/는/*range*/,/*end-expr*/는/*range*/+N이에요.R이 알려지지 않은 경계의 배열이거나 불완전 타입 배열이면, 프로그램은 ill-formed예요.
/*range*/의 타입이 클래스 타입C에 대한 참조이고,C의 범위에서 "begin"과 "end" 이름을 찾는 조회가 각각 적어도 하나의 선언을 찾으면,/*begin-expr*/는/*range*/.begin()이고/*end-expr*/는/*range*/.end()예요.- 그 외에는,
/*begin-expr*/는begin(/*range*/),/*end-expr*/는end(/*range*/)이에요. 여기서 "begin"과 "end"는 인자 의존 조회(ADL, 비-ADL 조회는 수행되지 않음)로 찾아요.
expansion-initializer가 비-배열 타입의 표현식이고, /*begin-expr*/와 /*end-expr*/ 둘 다 위 규칙으로 잘 구성되면, expansion-initializer는 확장-반복 가능(expansion-iterable) 이에요.
구조 분해 확장 문 (Destructuring expansion statement)
expansion-initializer가 확장-반복 가능하지 않은 표현식이면, 그 문은 구조 분해 확장 문이에요.
expansion-initializer 타입의 구조적 바인딩 크기를 N이라고 할 때, 구조 분해 확장 문은 expansion-initializer의 임시 객체 수명 확장(아래 참고)을 제외하면 다음과 동등해요. /* */로 감싼 변수와 표현식은 설명용이에요:
N이 0이면, 그 문은 다음과 동등해요:
{
init-statement
constexpr(선택) auto&& /*range*/ = (expansion-initializer);
}
- 그 외에는, 그 문은 다음과 동등해요:
{
init-statement
constexpr(선택) auto&& [/* u0, u1, ..., u(N-1) */] = (expansion-initializer);
{
item-declaration = /*v0*/;
compound-statement
}
// ...
{
item-declaration = /*v(N-1)*/;
compound-statement
}
}
/*range*/, /*begin*/, /*iter*/는 item-declaration에 constexpr 지정자가 있는 경우에만 constexpr로 선언돼요.
expansion-initializer가 lvalue이면 /*vI*/는 /*uI*/이고, 그렇지 않으면 /*vI*/는 static_cast<decltype(/*uI*/)&&>(/*uI*/)예요.
임시 확장 이니셜라이저 (Temporary expansion initializer)
열거 확장 문에서, 표현식 목록 expansion-initializer의 요소 expr 안에 임시 객체가 생성되고 그 객체가 expr의 전체 표현식 끝에서 파괴될 예정이면, 그 객체는 expr에서 초기화된 item-declaration의 수명 동안 유지돼요:
// foo()가 값을 반환한다면
template for (auto& x : {foo().items()}) { /* ... */ }
// 다음과 같이 확장됨:
{
// foo()가 반환한 임시 객체에 대해:
{
auto& x = foo().items(); // 객체는 세미콜론에서 파괴될 예정이지만,
// 블록 끝까지 수명이 연장됨
// ("x"의 수명과 같음)
{ /* ... */ } // 여기서 "x"를 사용하는 것은 잘 정의됨
}
}
반복·구조 분해 확장 문에서, expansion-initializer 안에 임시 객체가 생성되고 그 객체가 expansion-initializer 끝에서 파괴될 예정이면, 그 객체는 expansion-initializer에서 초기화된 참조의 수명 동안 유지돼요:
struct T
{
std::vector<int> vec{1, 2};
std::tuple<int> tup{3, 4};
};
template for (auto& x: T().vec) { /* ... */ }
// 다음과 같이 확장됨:
{
// T()가 반환한 임시 객체에 대해:
decltype(auto) range = (T().vec); // 객체는 세미콜론에서 파괴될 예정이지만,
// 그 수명은 블록 끝까지 연장됨
// ("range"의 수명과 같음)
auto begin = range.begin();
/* 확장된 복합 문들 */ // 여기서 벡터 요소에 접근하는 것은
// 잘 정의됨
}
template for (auto& x: T().tup) { /* ... */ }
// 다음과 같이 확장됨:
{
// T()가 반환한 임시 객체에 대해:
auto&& [u0, u1] = T().tup; // 객체는 세미콜론에서 파괴될 예정이지만,
// 블록 끝까지 수명 연장됨
// ("u0"과 "u1"의 수명과 같음)
/* 확장된 복합 문들 */ // 여기서 "u0"과 "u1"에 접근하는 것은
// 잘 정의됨
}
주의할 점 (Notes)
template for는 아직 C++26의 확장 문(expansion statement) 기능으로, __cpp_expansion_statements 매크로가 202506L 값을 가질 때 사용할 수 있어요.
키워드
template, for
예제
여기서는 세 가지 범주의 template for가 실제로 어떻게 쓰이는지 확인해 볼게요.
#include <array>
#include <vector>
constexpr int f1(const auto&... containers)
{
int result = 0;
template for (const auto& c : {containers...}) // 열거 확장 문
result += c[0];
return result;
}
constexpr int f2()
{
constexpr std::array<int, 3> arr{1, 2, 3};
int result = 0;
template for (constexpr int s : auto(arr)) // 반복 확장 문
result += s;
return result;
}
struct S
{
int i;
short s;
};
constexpr long f3(S s)
{
long result = 0;
template for (auto x : s) // 구조 분해 확장 문
result += sizeof(x);
return result;
}
int main()
{
constexpr int c1[] = {1, 2, 3};
constexpr int c2[] = {4, 3, 2, 1};
static_assert(f1(c1, c2) == 5); // c1[0] + c2[0]
static_assert(f2() == 6); // 1 + 2 + 3
static_assert(f3(S{}) == sizeof(int) + sizeof(short));
}
f1은 팩 containers...를 중괄호 표현식 목록으로 감싼 열거 확장이라, 각 컨테이너의 c[0]을 더해 1 + 4 = 5가 돼요. f2는 auto(arr)를 확장-반복 가능한 범위로 보고 각 요소 s를 더해 1+2+3 = 6이 돼요. f3은 S를 데이터 멤버로 구조 분해해서 sizeof(int) + sizeof(short)를 구해요. 모두 constexpr 문맥에서 컴파일 타임에 계산돼요.
더 알아보기
break문: 확장 문 안에서 루프를 조기 종료한다.continue문: 확장 문 안에서 현재 반복을 조기 종료한다.- 구조적 바인딩 선언(C++17부터): item-declaration으로 쓸 수 있다.