C ++의 메타 프로그래밍 소개

이전 1 2 3 페이지 3 페이지 3/3
  • 상태 변수 : 템플릿 매개 변수
  • 루프 구성 : 재귀를 통해
  • 실행 경로 선택 : 조건식 또는 전문화 사용
  • 정수 산술

재귀 인스턴스화의 양과 허용되는 상태 변수의 수에 제한이 없으면 계산 가능한 모든 것을 계산하기에 충분합니다. 그러나 템플릿을 사용하는 것은 편리하지 않을 수 있습니다. 또한 템플릿 인스턴스화에는 상당한 컴파일러 리소스가 필요하기 때문에 광범위한 재귀 인스턴스화는 컴파일러 속도를 빠르게 늦추거나 사용 가능한 리소스를 고갈시킵니다. C ++ 표준은 1,024 레벨의 재귀 인스턴스화를 최소로 허용하도록 권장하지만 강제하지는 않습니다. 이는 대부분의 (그러나 전부는 아님) 템플릿 메타 프로그래밍 작업에 충분합니다.

따라서 실제로 템플릿 메타 프로그램은 드물게 사용되어야합니다. 그러나 편리한 템플릿을 구현하기위한 도구로 대체 할 수없는 몇 가지 상황이 있습니다. 특히, 중요한 알고리즘 구현에서 더 많은 성능을 끌어 내기 위해 기존 템플릿의 내부에 숨겨져있을 수 있습니다.

재귀 인스턴스화 대 재귀 템플릿 인수

다음 재귀 템플릿을 고려하십시오.

템플릿 구조체 Doublify {}; template struct Trouble {using LongType = Doublify
   
    ; }; template struct Trouble {using LongType = double; }; 문제 :: LongType 아야;
   

의 사용 Trouble::LongType뿐만 아니라이의 재귀 인스턴스화를 트리거 Trouble, Trouble, ..., Trouble뿐만 아니라 인스턴스화 Doublify점점 더 복잡한 유형을 통해. 표는 성장 속도를 보여줍니다.

성장 Trouble::LongType

 
유형 별칭 기본 유형
Trouble::LongType double
Trouble::LongType Doublify
Trouble::LongType Doublify

   Doublify>

Trouble::LongType Doublify

     Doublify>,

  

     Doublify>>

표에서 보듯이, 표현식의 타입 정보의 복잡도가 Trouble::LongType기하 급수적으로 증가 N. 일반적으로 이러한 상황은 재귀 템플릿 인수를 포함하지 않는 재귀 인스턴스화보다 C ++ 컴파일러에 더 많은 스트레스를줍니다. 여기서 문제 중 하나는 컴파일러가 유형에 대해 잘린 이름의 표현을 유지한다는 것입니다. 이 망가진 이름은 어떤 방식 으로든 정확한 템플릿 전문화를 인코딩하고 초기 C ++ 구현에서는 템플릿 ID의 길이에 대략적으로 비례하는 인코딩을 사용했습니다. 이 컴파일러는 Trouble::LongType.

최신 C ++ 구현은 중첩 된 템플릿 ID가 최신 C ++ 프로그램에서 매우 일반적이라는 사실을 고려하고 영리한 압축 기술을 사용하여 이름 인코딩의 증가를 상당히 줄입니다 (예 : Trouble::LongType. 이러한 최신 컴파일러는 템플릿 인스턴스에 대해 실제로 저수준 코드가 생성되지 않기 때문에 실제로 필요하지 않은 경우 이름이 왜곡 된 이름을 생성하지 않습니다. 그럼에도 불구하고 다른 모든 것이 동일하기 때문에 템플릿 인수도 재귀 적으로 중첩 될 필요가없는 방식으로 재귀 인스턴스화를 구성하는 것이 좋습니다.

열거 형 값과 정적 상수

C ++ 초기에 열거 형 값은 클래스 선언에서 명명 된 멤버로 "진정한 상수"( 상수 표현식 이라고 함)를 만드는 유일한 메커니즘이었습니다 . 예를 들어, Pow3다음과 같이 3의 거듭 제곱을 계산 하는 메타 프로그램을 정의 할 수 있습니다.

meta / pow3enum.hpp // 3을 N 번째 템플릿으로 계산하는 기본 템플릿 struct Pow3 {enum {value = 3 * Pow3 :: value}; }; // 재귀 템플릿을 끝내기위한 완전한 전문화 struct Pow3 {enum {value = 1}; };

C ++ 98의 표준화는 클래스 내 정적 상수 이니셜 라이저 개념을 도입하여 Pow3 메타 프로그램은 다음과 같이 보일 수 있습니다.

meta / pow3const.hpp // 3을 N 번째 템플릿으로 계산하는 기본 템플릿 struct Pow3 {static int const value = 3 * Pow3 :: value; }; // 재귀 템플릿을 끝내기위한 완전한 전문화 struct Pow3 {static int const value = 1; };

그러나이 버전에는 단점이 있습니다. 정적 상수 멤버는 lvalue입니다. 따라서 다음과 같은 선언이있는 경우

무효 foo (int const &);

메타 프로그램의 결과를 전달합니다.

foo (Pow3 :: value);

컴파일러는의 주소 를 전달해야하며 Pow3::value이는 컴파일러가 정적 멤버에 대한 정의를 인스턴스화하고 할당하도록합니다. 결과적으로 계산은 더 이상 순수한 "컴파일 시간"효과로 제한되지 않습니다.

열거 형 값은 lvalue가 아닙니다 (즉, 주소가 없음). 따라서 참조로 전달할 때 정적 메모리가 사용되지 않습니다. 계산 된 값을 리터럴로 전달한 것과 거의 동일합니다.

그러나 C ++ 11에는 constexpr정적 데이터 멤버가 도입 되었으며 이러한 멤버는 정수 형식으로 제한되지 않습니다. 그들은 위에서 제기 한 주소 문제를 해결하지 못하지만 그 단점에도 불구하고 이제는 메타 프로그램의 결과를 생성하는 일반적인 방법입니다. 그것들은 (인공적인 열거 형과 반대되는) 올바른 유형을 갖는 장점이 있으며, 그 유형은 자동 유형 지정자로 정적 멤버가 선언 될 때 추론 될 수 있습니다. C ++ 17은 위에서 제기 된 주소 문제를 해결하는 인라인 정적 데이터 멤버를 추가했으며 constexpr.

메타 프로그래밍 역사

메타 프로그램의 가장 초기에 문서화 된 예는 Erwin Unruh가 C ++ 표준화위원회에서 Siemens를 대표했습니다. 그는 템플릿 인스턴스화 프로세스의 계산 완전성을 주목하고 첫 번째 메타 프로그램을 개발하여 자신의 요점을 입증했습니다. 그는 Metaware 컴파일러를 사용하여 연속적인 소수를 포함하는 오류 메시지를 발행하도록 유도했습니다. 다음은 1994 년 C ++위원회 회의에서 회람 된 코드입니다 (현재 표준 준수 컴파일러에서 컴파일되도록 수정 됨).

meta / unruh.cpp // 소수 계산 // (Erwin Unruh에 의해 1994 년 원본 허가로 수정 됨) 템플릿
   
     struct is_prime {enum ((p % i) && is_prime2? p : 0), i-1> :: pri); }; 템플릿 구조체 is_prime {enum {pri = 1}; }; 템플릿 구조체 is_prime {enum {pri = 1}; }; 주형
    
      struct D { D(void*); }; template
     
       struct CondNull { static int const value = i; }; template struct CondNull { static void* value; }; void* CondNull::value = 0; template
      
        struct Prime_print {
       

// primary template for loop to print prime numbers Prime_print a; enum { pri = is_prime::pri }; void f() { D d = CondNull::value;

// 1 is an error, 0 is fine a.f(); } }; template struct Prime_print {

// full specialization to end the loop enum {pri=0}; void f() { D d = 0; }; }; #ifndef LAST #define LAST 18 #endif int main() { Prime_print a; a.f(); }

If you compile this program, the compiler will print error messages when, in Prime_print::f(), the initialization of d fails. This happens when the initial value is 1 because there is only a constructor for void*, and only 0 has a valid conversion to void*. For example, on one compiler, we get (among several other messages) the following errors:

unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’ unruh.cpp:39:14: error: no viable conversion from ’const int’ to ’D’

Note: As error handling in compilers differs, some compilers might stop after printing the first error message.

The concept of C++ template metaprogramming as a serious programming tool was first made popular (and somewhat formalized) by Todd Veldhuizen in his paper “Using C++ Template Metaprograms.” Veldhuizen’s work on Blitz++ (a numeric array library for C++) also introduced many refinements and extensions to metaprogramming (and to expression template techniques).

Both the first edition of this book and Andrei Alexandrescu’s Modern C++ Design contributed to an explosion of C++ libraries exploiting template-based metaprogramming by cataloging some of the basic techniques that are still in use today. The Boost project was instrumental in bringing order to this explosion. Early on, it introduced the MPL (metaprogramming library), which defined a consistent framework for type metaprogramming made popular also through David Abrahams and Aleksey Gurtovoy’s book C++ Template Metaprogramming.

Additional important advances have been made by Louis Dionne in making metaprogramming syntactically more accessible, particularly through his Boost.Hana library. Dionne, along with Andrew Sutton, Herb Sutter, David Vandevoorde, and others are now spearheading efforts in the standardization committee to give metaprogramming first-class support in the language. An important basis for that work is the exploration of what program properties should be available through reflection; Matúš Chochlík, Axel Naumann, and David Sankel are principal contributors in that area.

John J. Barton and Lee R. Nackman illustrated how to keep track of dimensional units when performing computations. The SIunits library was a more comprehensive library for dealing with physical units developed by Walter Brown. The std::chrono component in the standard library only deals with time and dates, and was contributed by Howard Hinnant.