템플릿 메타프로그래밍

Template metaprogramming

템플릿 메타프로그래밍(TMP)은 컴파일러템플릿을 사용하여 임시 소스 코드를 생성하는 메타프로그래밍 기법으로, 컴파일러가 나머지 소스 코드와 병합한 후 컴파일러를 컴파일한다.이러한 템플릿의 출력은 컴파일 시간 상수, 데이터 구조 및 전체 함수를 포함할 수 있다.템플릿의 사용은 컴파일 시간 다형성이라고 생각할 수 있다.이 기술은 여러 언어에서 사용되는데, 가장 잘 알려진 것은 C++이지만, Curl, D, Nim, XL도 있다.

템플리트 메타프로그래밍은 어떤 의미에서 우연히 발견되었다.[1][2]

일부 다른 언어는 더 강력하지는 않더라도 유사한 컴파일 시간 시설(예: Lisp 매크로)을 지원하지만, 이러한 언어는 본 문서의 범위를 벗어난다.

템플릿 메타프로그래밍 구성 요소

메타프로그래밍 기법으로 템플릿을 사용하려면 두 가지 뚜렷한 연산이 필요하다. 즉, 템플릿은 정의되어야 하고, 정의된 템플릿은 인스턴스화되어야 한다.템플릿 정의는 생성된 소스 코드의 일반 형식을 설명하며, 인스턴스화는 템플릿의 일반 형식에서 특정 소스 코드 세트를 생성하게 한다.

템플릿 메타프로그래밍은 튜링-완전하다. 컴퓨터 프로그램에 의해 표현 가능한 모든 계산은 어떤 형태로든 템플릿 메타프로그래프로 계산될 수 있다.[3]

템플릿은 매크로와 다르다.매크로란 컴파일 시간에 실행되어 컴파일될 코드의 텍스트 조작(예: C++ 매크로)을 수행하거나 컴파일러에서 생성되는 추상 구문 트리(예: 러스트 또는 리스프 매크로)를 조작하는 코드 조각이다.텍스트 매크로는 컴파일 직전에 소스 코드의 인메모리 텍스트만 변경하기 때문에 조작되는 언어의 구문과 더 독립적이다.

템플릿 메타프로그래프는 변이 가능한 변수가 없다. 즉, 일단 초기화되면 변수가 값을 변경할 수 없기 때문에 템플릿 메타프로그래밍은 기능 프로그래밍의 한 형태로 볼 수 있다.실제로 많은 템플릿 구현은 아래 예에서 보는 바와 같이 재귀성을 통해서만 흐름 제어를 구현한다.

템플릿 메타프로그래밍 사용

템플리트 메타프로그래밍의 구문은 보통 그것이 사용하는 프로그래밍 언어와 매우 다르지만, 실용적인 용도를 가지고 있다.템플릿을 사용하는 일반적인 이유로는 일반 프로그래밍(일부 사소한 변형을 제외하고 유사한 코드의 섹션 회피)을 구현하거나 프로그램이 실행될 때마다(예를 들어 컴파일러 루프를 제거하여)가 실행되지 않고 컴파일 시간에 한 번 수행하는 것과 같은 자동 컴파일 시간 최적화를 수행하기 위한 것이다.프로그램이 실행될 때마다 점프와 루프 수가 감소한다.

컴파일 시간 클래스 생성

정확히 "컴파일 시간에 프로그래밍"이 의미하는 것은 요인 함수의 예를 들어 설명할 수 있는데, 비템플릿 C++는 다음과 같이 재귀어를 사용하여 작성할 수 있다.

서명이 없는 요인의(서명이 없는 n) {  돌아오다 n == 0 ? 1 : n * 요인의(n - 1);  }  // 사용 예제: // 요인(0)은 1을 산출한다. // 요인(4)은 24를 산출한다. 

위의 코드는 런타임에 실행되어 리터럴 0과 4의 요인 값을 결정한다.템플릿 메타프로그래밍과 템플릿 전문화를 사용하여 재귀의 종료 조건을 제공함으로써 프로그램에 사용된 요인(사용되지 않은 요인 무시)은 컴파일 시간에 다음 코드로 계산할 수 있다.

템플릿 <서명이 없는 N> 구조상의 요인의 {  정태의 경구적 서명이 없는 가치를 매기다 = N * 요인의<N - 1>::가치를 매기다; };  템플릿 <> 구조상의 요인의<0> {  정태의 경구적 서명이 없는 가치를 매기다 = 1; };  // 사용 예제: // 요인 설계<0>::값으로 1; // 요인[4]::값으로 24를 산출할 수 있다. 

위의 코드는 컴파일 시 리터럴 0과 4의 요인 값을 계산하고 그 결과를 사전 계산된 상수인 것처럼 사용한다.이러한 방법으로 템플릿을 사용할 수 있으려면 컴파일러는 컴파일 시간에 매개변수의 값을 알아야 하는데, 이는 컴파일 시간에 X를 알 수 있는 경우에만 요인 <X>:값의 자연적인 전제조건이 있다.즉 X는 일정한 리터럴이나 상수 표현이어야 한다.

C++11C++20에서는 컴파일러가 코드를 실행할 수 있도록 constexpr과 consteval이 도입되었다.constexpr과 consteval을 사용하면 비임시 구문에 일반적인 재귀 요인 정의를 사용할 수 있다.[4]

컴파일 시간 코드 최적화

위의 요인 예는 프로그램에서 사용하는 모든 요인들을 사전 컴파일하여 컴파일할 때 숫자 상수로 주입하여 런타임 오버헤드와 메모리 공간을 모두 절약한다는 점에서 컴파일 시간 코드 최적화의 한 예다.그러나 그것은 비교적 작은 최적화다.

또 다른 중요한 예로, 컴파일 시간 루프 언롤링의 경우 템플릿 메타프로그래밍을 사용하여 길이-n 벡터 클래스(컴파일 시간에 n을 알 수 있음)를 생성할 수 있다.보다 전통적인 length-n 벡터에 대한 이점은 루프를 열 수 있어 매우 최적화된 코드를 얻을 수 있다는 것이다.예를 들어 추가 연산자를 생각해 보십시오.길이-n 벡터 추가는 다음과 같이 기록될 수 있다.

템플릿 <인트로 길이> 벡터<길이>& 벡터<길이>::운영자+=(경시하다 벡터<길이>& rhs.)  {     을 위해 (인트로 i = 0; i < 길이; ++i)         가치를 매기다[i] += rhs..가치를 매기다[i];     돌아오다 *; } 

컴파일러가 위에서 정의한 기능 템플릿을 인스턴스화할 때 다음과 같은 코드가 생성될 수 있다.[citation needed]

템플릿 <> 벡터<2>& 벡터<2>::운영자+=(경시하다 벡터<2>& rhs.)  {     가치를 매기다[0] += rhs..가치를 매기다[0];     가치를 매기다[1] += rhs..가치를 매기다[1];     돌아오다 *; } 

컴파일러의 최적기는 롤링을 해제할 수 있어야 한다.for템플릿 매개 변수 때문에 반복length컴파일 시 상수.

그러나 이는 인스턴스화되는 각 'N'(벡터 크기)에 대해 별도의 미연속 코드가 생성되므로 코드 비대화를 유발할 수 있으므로 주의하고 주의하십시오.

정적 다형성

다형성(多形性)은 이 코드에서와 같이 파생된 객체를 그 기본 객체의 인스턴스로 사용할 수 있지만 파생된 객체의 방법이 호출되는 일반적인 표준 프로그래밍 시설이다.

계급 베이스 { 공중의:     가상의 공허하게 하다 방법() { 찌꺼기::뻐드렁니가 나다 << "베이스"; }     가상의 ~베이스() {} };  계급 파생된 : 공중의 베이스 { 공중의:     가상의 공허하게 하다 방법() { 찌꺼기::뻐드렁니가 나다 << " 파생됨"; } };  인트로 본래의() {     베이스 *pBase = 새로운 파생된;     pBase->방법(); //출력 "파생"     삭제하다 pBase;     돌아오다 0; } 

의 모든 발명이 있는 곳에virtual방법은 가장 많이 사용되는 계층의 방법이 될 것이다.이러한 동적 다형성 행동은 (일반적으로) 가상 방법을 사용하는 클래스에 대한 가상 검색 테이블을 생성하여 (일반적으로) 호출할 방법을 식별하기 위해 런타임에 통과되는 테이블을 생성함으로써 얻어진다.따라서 런타임 다형성은 반드시 실행 오버헤드를 수반한다(현대식 아키텍처에서는 오버헤드가 작지만).

그러나, 많은 경우에 필요한 다형성 행동은 불변하며 컴파일 시간에 결정될 수 있다.그런 다음, 프로그래밍 코드에서 다형성을 모방하지만 컴파일 시간에 해결되어 런타임 가상 테이블 검색을 없앤 정적 다형성을 달성하기 위해 CRTP(Gurious Repeat Template Pattern)를 사용할 수 있다.예를 들면 다음과 같다.

템플릿 <계급 파생된> 구조상의 밑의 {     공허하게 하다 접점()     {          // ...          정적_캐스트<파생된*>()->실행();          // ...     } };  구조상의 파생된 : 밑의<파생된> {      공허하게 하다 실행()      {          // ...      } }; 

여기서 기본 클래스 템플릿은 회원 기능 본체가 선언이 끝날 때까지 인스턴스화되지 않는다는 점을 이용할 것이며, 그것은 a의 사용을 통해 자체 회원 기능 내에서 파생된 클래스의 멤버를 사용할 것이다.static_cast 따라서 다형성 특성을 가진 객체 구성을 생성하는 컴파일에서.실제 사용의 예로서 CRTP는 Boostiterator 라이브러리에서 사용된다.[5]

또 다른 유사한 용도는 "제한된 템플릿 확장"이라고도 하는 "Barton-Nackman 속임수"인데, 여기서 공통 기능성은 계약으로 사용되지 않고 코드 중복성을 최소화하면서 순응적 행동을 시행하는 데 필요한 구성요소로 사용되는 기본 클래스에 배치할 수 있다.

정적 테이블 생성

정적 테이블의 이점은 "비용" 계산을 단순한 배열 인덱싱 작업으로 대체하는 것이다(예: 룩업 테이블 참조).C++에는 컴파일 시간에 정적 테이블을 생성할 수 있는 두 가지 이상의 방법이 있다.다음 목록은 재귀적 구조와 가변적 템플릿을 사용하여 매우 간단한 표를 만든 예를 보여준다.그 탁자의 크기는 10이다.각 값은 지수의 제곱이다.

#include <아이오스트림> #include <배열>  경구적 인트로 테이블_사이즈 = 10;  /** * 재귀적 도우미 구조를 위한 가변 템플릿. */ 템플릿<인트로 색인 = 0, 인트로 ...D> 구조상의 도우미 : 도우미<색인 + 1, D..., 색인 * 색인> { };  /** * 테이블 크기가 TAB에 도달할 때 재귀 작업을 종료하기 위한 템플릿 전문화 TABLE_SIZE. */ 템플릿<인트로 ...D> 구조상의 도우미<테이블_사이즈, D...> {   정태의 경구적 찌꺼기::배열하다<인트로, 테이블_사이즈> 테이블 = { D... }; };  경구적 찌꺼기::배열하다<인트로, 테이블_사이즈> 테이블 = 도우미<>::테이블;  열거하다  {    = 테이블[2] // 시간 사용 컴파일 };  인트로 본래의() {   을 위해(인트로 i=0; i < 테이블_사이즈; i++) {     찌꺼기::뻐드렁니가 나다 << 테이블[i]  << 찌꺼기::끝을 맺다; // 실행 시간 사용   }   찌꺼기::뻐드렁니가 나다 << "4: " <<  << 찌꺼기::끝을 맺다; } 

이를 뒷받침하는 아이디어는 템플릿의 전문화가 10개 요소의 크기로 재귀성을 종료할 때까지 구조체 도우미가 템플릿 인수가 하나 더 있는 구조체(이 예에서는 INDEX * ENDEX로 계산됨)로부터 재귀적으로 상속된다는 것이다.전문화는 단순히 변수 인수 목록을 배열의 요소로 사용한다.컴파일러는 다음과 유사한 코드를 생성한다(-Xclang -ast-print -fsyntax 전용으로 호출된 clang에서 호출됨).

템플릿 <인트로 색인 = 0, 인트로 ...D> 구조상의 도우미 : 도우미<색인 + 1, D..., 색인 * 색인> { }; 템플릿<> 구조상의 도우미<0, <>> : 도우미<0 + 1, 0 * 0> { }; 템플릿<> 구조상의 도우미<1, <0>> : 도우미<1 + 1, 0, 1 * 1> { }; 템플릿<> 구조상의 도우미<2, <0, 1>> : 도우미<2 + 1, 0, 1, 2 * 2> { }; 템플릿<> 구조상의 도우미<3, <0, 1, 4>> : 도우미<3 + 1, 0, 1, 4, 3 * 3> { }; 템플릿<> 구조상의 도우미<4, <0, 1, 4, 9>> : 도우미<4 + 1, 0, 1, 4, 9, 4 * 4> { }; 템플릿<> 구조상의 도우미<5, <0, 1, 4, 9, 16>> : 도우미<5 + 1, 0, 1, 4, 9, 16, 5 * 5> { }; 템플릿<> 구조상의 도우미<6, <0, 1, 4, 9, 16, 25>> : 도우미<6 + 1, 0, 1, 4, 9, 16, 25, 6 * 6> { }; 템플릿<> 구조상의 도우미<7, <0, 1, 4, 9, 16, 25, 36>> : 도우미<7 + 1, 0, 1, 4, 9, 16, 25, 36, 7 * 7> { }; 템플릿<> 구조상의 도우미<8, <0, 1, 4, 9, 16, 25, 36, 49>> : 도우미<8 + 1, 0, 1, 4, 9, 16, 25, 36, 49, 8 * 8> { }; 템플릿<> 구조상의 도우미<9, <0, 1, 4, 9, 16, 25, 36, 49, 64>> : 도우미<9 + 1, 0, 1, 4, 9, 16, 25, 36, 49, 64, 9 * 9> { }; 템플릿<> 구조상의 도우미<10, <0, 1, 4, 9, 16, 25, 36, 49, 64, 81>> {   정태의 경구적 찌꺼기::배열하다<인트로, 테이블_사이즈> 테이블 = {0, 1, 4, 9, 16, 25, 36, 49, 64, 81}; }; 

C++17 이후, 이것은 다음과 같이 더 읽기 쉽게 쓰여질 수 있다.

  #include <아이오스트림> #include <배열>  경구적 인트로 테이블_사이즈 = 10;  경구적 찌꺼기::배열하다<인트로, 테이블_사이즈> 테이블 = [] { // OR: constexpr 자동 테이블   찌꺼기::배열하다<인트로, 테이블_사이즈> A = {};   을 위해 (서명이 없는 i = 0; i < 테이블_사이즈; i++) {     A[i] = i * i;   }   돌아오다 A; }();  열거하다  {    = 테이블[2] // 시간 사용 컴파일 };  인트로 본래의() {   을 위해(인트로 i=0; i < 테이블_사이즈; i++) {     찌꺼기::뻐드렁니가 나다 << 테이블[i]  << 찌꺼기::끝을 맺다; // 실행 시간 사용   }   찌꺼기::뻐드렁니가 나다 << "4: " <<  << 찌꺼기::끝을 맺다; } 

좀 더 정교한 예를 보여주기 위해 다음 목록의 코드가 확장되어 값 계산 도우미, 테이블 특정 오프셋 및 테이블 값 유형에 대한 템플릿 인수(예: uint8_t, uint16_t, ...).

                                                                 #include <아이오스트림> #include <배열>  경구적 인트로 테이블_사이즈 = 20; 경구적 인트로 오프셋 = 12;  /** * 단일 테이블 항목을 계산하는 템플릿 */ 템플릿 <타이프 이름 밸류티페, 밸류티페 오프셋, 밸류티페 색인> 구조상의 밸류헬퍼 {   정태의 경구적 밸류티페 가치를 매기다 = 오프셋 + 색인 * 색인; };  /** * 재귀적 도우미 구조를 위한 가변 템플릿. */ 템플릿<타이프 이름 밸류티페, 밸류티페 오프셋, 인트로