본문 바로가기

3.구현/C or C++

Metaprogramming

아래글은 내가 작성한 글은 아니다. 정보문화사의 boost로 배우는 개념, 도구, 기법에 나오는 내용 중 일부 pdf 문서이다.

Metaprogramming

1. 도입

이번 장은 이 책 나머지 부분을 위한 몸 풀기에 해당하는 것으로, 기본적인 개념들과 용어들을 간략하게나마 개괄한다. 이 책이 다루는 내용에 대한 독자의 사전 지식을 미리 시험해볼 기회도 가질 수 있을 것이다. 이번 장을 다 읽고 나면 이 책이 무엇에 대한 책인지 어느정도 감을 잡을 수 있을 것이며, (바라건대)나머지 장들의 좀 더 큰 개념들로 나아가고자하는 열망도 생기게 될 것이다.

1.1 시작하며

템플릿 메타프로그램(metaprogram)의 멋진 점 하나는, 전통적인 시스템에서 볼 수 있는 한가지 바람직한 특성을 가지고 있다는 것이다. 어떤 것이냐 하면, 일단 하나의 메타프로그램을 작성하고 나면 내부적인 작동 방식을 모르고서도 그 프로그램을 사용할 수 있다는점이다.

그게 사실일까? 템플릿 메타프로그래밍으로 구현된 유틸리티 하나를 사용하는 간단한 C++ 프로그램으로 확인해보자.

#include "libs/mpl/book/chapter1/binary.hpp"
#include <iostream>
int main()
{
  std::cout<<binary<101010>::value<<std::endl;
  return 0;
}

이진수에 대해 잘 알고 있는 독자라면 이 프로그램을 실행하지 않고도 출력을 짐작할 수 있을 것이다. 그래도 직접 한 번 컴파일하고 실행해보기 바란다. 앞에서 말한 템플릿 메타프로그래밍의 특징을 확인한다는 의미 외에도, 독자의 컴파일러가 이 책에 나오는 코드를 제대로 다룰 수 있는지 시험해보는 기회일 수 있기 때문이다. 이 프로그램을 컴파일하고

실행하면, 이진수 101010에 해당하는 다음과 같은 십진수 값이 표준 출력으로 출력되어야 한다.

42

1.2 메타프로그램이란?

메타프로그램(metaprogram)을 단어 뜻 그대로 해석한다면 프로그램에 대한 프로그램 1)이라고 할 수 있다. 좀 더 풀어서 이야기하면, 메타프로그램은 코드(프로그램)를 조작하는 프로그램이다. 좀 생소한 개념일 수도 있겠지만, 사실은 대부분의 개발자들에게 이미 익숙한것이다. C++ 컴파일러도 바로 그런 프로그램이다. C++ 컴파일러는 C++ 코드를 조작해서어셈블리 언어 또는 기계어 코드를 만들어 낸다.

YACC [Joh79] 같은 파서 생성기 역시 프로그램을 조작하는 프로그램이다. YACC에 대한입력은 문법 규칙들과 그에 부착된(중괄호 안에 있는) 행동들로 작성된 하나의 고수준 파서 서술문이다. 예를 들어서, 다음은 통상적인 우선순위 규칙들을 가진 산술 표현식을 분석, 평가하기 위해서 YACC에 넣는 문법 서술문이다.

expression : term
| expression ' + ' term { $$ = $1 + $3 ; }
| expression ' - ' term { $$ = $1 - $3 ; }
;
term : factor
| term ' * ' factor { $$ = $1 * $3 ; }
| term ' / ' factor { $$ = $1 / $3 ; }
;
factor : INTEGER
| group
;
group : ' ( ' expression ' ) '
;

1) 철학에서, 그리고 프로그래밍에서도, 메타 (meta) 라는 접두어는 ~에 대한 또는 한 단계 높은 수준에서의 서술 을 의미한다. 그러한 의미는 원래의 그리스어에서의 의미인 ~ 위에 (beyond) , ~ 뒤에 (behind) 에서 비롯된 것이다.

1.3 호스트 언어 안에서의 메타프로그래밍 19

이러한 입력에 대해 YACC는 하나의 yypar se 함수를(그 외에도 여러 가지가 있겠지만) 포함하는 C/C++ 소스 파일을 생성한다. 실제 프로그램은 주어진 텍스트에 대해 그 yypar se를 호출해서 그 텍스트를 해석하고 적절한 행동을 수행하게 된다.2)

int main ()
{
  extern int yypa rse () ;
  retur nyyparse ();
}

YACC의 사용자는 주로 파서 설계의 영역(domain) 안에서 작업을 하므로, YACC의 입력 언어를 이 시스템의 영역 언어(domain language)라고 부르기로 하자. 대체로, 사용자의 프로그램의 나머지 부분은 범용 프로그래밍 시스템을 필요로 하며 YACC가 생성한 파서와 상호작용을 해야 할 것이다. 그러한 용도로 쓰이는 언어를 호스트 언어라고 한다. 호스트 언어(host language)는 사용자가 실제로 컴파일하고 자신의 다른 코드와 함께 링크하는 언어를 뜻하며, 이 예에서는 C 또는 C++이다. 결론적으로, YACC는 영역 언어를 호스트 언어로 번역하는 메타프로그램이라 할 수 있다. 이러한 시스템에서 영역 언어는 두 단계의 번역 과정을 거치며, 사용자는 영역 언어와 프로그램의 나머지 부분 사이의 경계를 항상 확실하게 인식한다.

1.4 호스트 언어 안에서의 메타프로그래밍

YACC는 번역기(translator), 즉 영역 언어와 호스트 언어가 다른 메타프로그램의 한 예이다. Scheme [SS75] 같은 언어에서는 좀 더 흥미로운 형태의 메타프로그래밍이 가능하다. Scheme 메타프로그래머는 자신의 영역 언어를 Scheme의 유효한 프로그램 자체의 한 부분 집합으로 정의하며, 그러한 언어로 된 메타프로그램은 프로그램의 나머지 부분을 처리하는 데 쓰이는 것과 동일한 번역 단계 안에서 수행된다. 프로그래머는 보통의 프로그래밍과 메타프로그래밍, 그리고 영역 언어를 이용한 프로그래밍 사이를 그 경계를 인식하지 않고도 넘나들곤하며, 그럼으로써 같은 시스템 안에서 여러 영역들을 매끄럽게 결합할 수 있게 된다.

놀랍게도, C++ 컴파일러에도 정확히 그런 방식의 메타프로그래밍 능력이 숨어있다. 이 책의 나머지 부분은 그러한 능력을 어떻게 일깨우는지, 그리고 그러한 능력을 언제, 어떻게 사용하는지에 대한 것이다.

2) 이 코드 예제는 텍스트를 토큰화하는 적절한 yyl ex 함수가 구현되어 있다고 가정한 것이다. 10장에는 YACC에 대한 완전한 예제가 하나 나온다. 물론, YACC를 배우기 위해서는 YACC 매뉴얼을 참고하는 것이 좋다.

1.5 C++의 메타프로그래밍

템플릿 메커니즘이 원래부터 메타프로그래밍을 염두에 두고 설계된 것은 아니다. 템플릿 메커니즘이 호스트 언어 메타프로그래밍을 위한 풍부한 수단들을 제공한다는 점은 나중에야 발견된 것이다. 그런 면에서 C++의 메타프로그래밍은 우연하게 발견되었다고 할 수 있다 [Unruh94], [Veld95b]. 이번 절에서는 C++에서의 메타프로그래밍의 기본적인 메커니즘들과 흔히 쓰이는 몇 가지 관용구(idiom, 숙어)들을 살펴보겠다.

1.5.1 수치 계산

초기의 C++ 메타프로그램들은 컴파일 시점에서 정수 계산을 수행하는 것이었다. 한 C++ 위원회 회의에서, Erwin Unruh는 최초의 C++ 메타프로그램들 중 하나를 발표했다. 그 프로그램은 사실 문법에 맞지 않는 위법한 코드였는데, 놀랍게도 오류 메시지들을 통해서 일련의 계산된 소수(素數)들을 출력했다.

위법한 코드가 실제의 좀 더 큰 시스템에서 효과적으로 쓰이기는 어려우므로, 그 프로그램을 여기서 제시하지는 않겠다. 대신 좀 더 실용적인 예를 보자. 다음의 메타프로그램은 앞에 나온 컴파일러 테스트 예제의 핵심부에 해당하는 것으로, 이진수 형태의 부호 없는 십진수 정수를 그 값이 의미하는 실제 이진수에 해당하는 십진수 값으로 번역하는 것이다(번역된 결과는 하나의 정수 상수가 된다).

template <unsigned long N>
struct binary
{
  static unsigned const value
  = binary<N/10>::value * 2 // 더 높은 비트들을
  + N;                    // 최하위 비트 앞에 붙인다.
}
template <> // 특수화
struct binary<0> // 재귀 종료
{
  static unsigned const value = 0;
};
unsigned const one = binary<1>::value ;
unsigned const three = binary<11>::value ;
unsigned const five = binary<101>::value ;
unsigned const seven = binary<111>::value ;
unsigned const nine = binary<1001>::value ;

코드에는 템플릿과 변수 정의만 있다. 과연 프로그램은 어디 있는 것일까? 그런 의문이 든다면, binary의 내포된 ::value 멤버에 접근했을 때 어떤 일이 일어나는지 생각해보기바란다. 이 binary 템플릿은 N이 0이 될 때까지 점점 더 작은 N에 대해 인스턴스화된다. 0에 대한 템플릿 특수화는 재귀 종료 조건으로 쓰이는 것이다. 아마 재귀 함수를 떠올렸을 수도 있을 텐데, 사실 프로그램이라는 것도 하나의 함수라는 사실에 생각이 미쳤다면 이것을 왜 메타 프로그램 이라고 부르는지 이해할 수 있을 것이다. 본질적으로, 이 작은 메타 프로그램은 컴파일러가 컴파일 도중에 해석(interpret)하는 하나의 프로그램이다.

오류 점검

사용자가 678 같은, 이진수 형태가 아닌 값을 사용할 수도 있다. 그러면 6x22 + 7x21 + 8x20 같은 무의미한 결과가 나오게 된다. 물론 이는 사용자의 실수에 해당하겠지만, 그런 실수도 컴파일러가 지적해준다면 좋을 것이다. 3장에는 N이 0과 1로만 구성된 십진수 값일 때에만 binary::value가 컴파일되도록 하는 방법이 나온다.

C++ 언어는 컴파일 시점에서의 표현식과 실행시점 계산을 구분하므로, 메타프로그램과 그에 해당하는 실행시점 코드는 모습이 다를 수밖에 없다. Scheme에서와 마찬가지로, C++의 경우 역시 보통의 프로그램과 메타프로그램 모두 같은 언어로 작성하지만, 컴파일 시점에서 언어의 모든 것을 사용할 수는 없다는 점에서는 차이를 보인다. C++ 메타프로그래밍에 사용할 수 있는 것은 C++ 전체의 한 부분집합일 뿐이다. 이전의 binary 예제를 다음의 실행시점 버전과 비교해보자.

unsigned binary (unsigned long N)
{
  return N == 0 ? 0 : N + 2 * binary (N/ 10);
}

핵심적인 차이는 종료 조건을 처리하는 방식에 있다. 메타프로그램에서는 N이 0이 되었을때의 행동을 템플릿 특수화(template specialization)를 통해 서술하지만, 실행시점에서는 조건문을 통해서 서술한다. 템플릿 특수화는 거의 모든 C++ 메타프로그램들에서 볼 수 있는 (메타프로그래밍 라이브러리의 인터페이스 뒤에 숨어있어서 보이지 않을 수도 있지만) 공통의 수단이다.

또 다른 중요한 차이는 반복의 수단이다. 이 차이가 좀 더 잘 드러나도록, 위의 실행시점 버전을 재귀 대신 for 루프로 바꿔보자.

unsigned binary ( unsigned long N)
{
  unsigned result = 0 ;
  for ( unsigned bit = 0x1 ; N; N /= 10 , bit <<= 1)
  {
     if (N)
        result += bit;
  }
  return result;
}

재귀 버전에 비해서 코드가 좀 장황해졌지만, 이해하기는 보다 쉬워졌다고 느끼는 C++ 프로그래머들이 많을 것이다. 그리고 실행시점 반복이 종종 재귀보다 효율적이라는 점 역시 중요할 수 있다.

C++의 컴파일 시점 부분을 순수 함수적 언어(pure functional language) 라고 부르기도 한다. 이는 C++ 컴파일 시점 부분이 Haskell 같은 함수적 언어처럼 (메타)자료는 불변이(immutable)이며 (메타)함수는 부수 효과(side effect)를 가지지 않는다 라는 속성을 가지고 있기 때문이다. 좀 더 설명하자면, 컴파일 시점 C++에는 실행시점 C++에 쓰이는 비 const 변수에 해당하는 것이 없다. 그리고, 종료 조건 안에서 어떠한 형태이든 가변이(mutable) 상태를 점검할 수 없다면 유한한 루프를 작성할 수가 없다. 간단히 말해서 컴파일 시점 C++에서는 루프 반복이 불가능하며, 반복이 필요한 경우에는 항상 재귀를 사용해야 한다.

1.5.2 형식의 계산

C++ 템플릿 메타프로그래밍에서, 컴파일 시점 수치 계산 능력보다 훨씬 더 중요한 것은 형식(type)들을 계산하는 능력이다. 실제로, 이 책 나머지 부분은 거의 모두 형식 계산을 다루고 있으며, 다음 장의 첫 번째 절부터 형식 계산에 대한 예제가 나온다. 그 때부터는 템플릿 메타프로그래밍을 형식을 다루는 계산 이라고 생각하게 될 것이다. 형식 계산의 세부적인 사항은 2장에서 이야기하게 되지만, 여기서 형식 계산의 힘을 잠깐 맛보고 넘어가는 것도 좋을 것 같다. 앞에서 YACC 표현식 평가의 예가 나왔었는데, YACC 없이도 그런 일이 가능하다. 다음의 유효한 C++ 코드는 앞의 YACC 예와 같은 일을 한다 (Boost Spirit 라이브러리의 적절한 코드로 감싸여 있다고 할 때).

expr =
( term[expr .val = _ 1] >> ' + ' >> expr [expr .val += _ 1] )
| ( term[expr .val = _ 1] >> ' - ' >> expr [expr .val - = _ 1] )
| term[expr .val = _ 1]

이 코드에서, 각각의 배정문은 그 우변에 있는 문법 규칙을 해석, 평가하는 함수 객체를 좌변의 변수에 저장한다. 저장된 각 함수 객체가 호출되었을 때의 행동방식은 전적으로 그 함수 객체를 생성할 때 쓰인 표현식의 형식에 의해 결정되며, 각 표현식의 형식은 표현식에 쓰인 여러 연산자들에 연관된 한 메타프로그램이 계산한다.

YACC와 마찬가지로, Spirit 라이브러리는 문법 명세로부터 파서를 생성하는 하나의 메타프로그램이다. 그러나 YACC와 달리 Spirit의 영역 언어는 C++ 자체의 부분집합이다. 그런 것이 어떻게 가능한지 지금 당장 이해되지 않는다고 해서 걱정할 필요는 없다. 이 책을 다 읽고 나면 충분히 이해할 수 있을 것이다.

1.6 메타프로그래밍이 필요한 이유

;
term =
( factor [term.val = _1] >> '*' >> term[term.val *= _1] )
| ( factor [term.val = _1] >> '/' >> term[term.val / = _1] )
| factor [term.val = _1]
;
factor =
integer [factor.val = _1]
| ( ' ( ' >> expr [factor.val = _1] >> ' ) ' )
;

그런데, 메타프로그래밍의 이점은 무엇일까? 사실 지금까지 논의한 문제들의 경우 메타프로그래밍을 사용하지 않고도 더 간단하게 수행할 수 있는 방법들이 많이 존재한다. 그럼 메타프로그래밍 대신 사용할 수 있는 두 가지 접근방식과, 그것들을 이진수 표현 예제와 파서 생성 예제에 적용한다고 했을 때 메타프로그래밍에 비해 어떤 장단점을 가지는지를 살펴보자.

1.6.1 대안 1: 실행시점 계산

가장 단순한 접근방식은, 그냥 컴파일 시점이 아니라 실행시점에서 계산을 수행하는 것이다. 앞에서 이미 binary 함수 구현을 두 가지나 살펴보았는데, 파서 생성 역시 실행시점에서 문법 명세를 해석해서 파서를 생성할 수 있다. 메타프로그램을 사용하는 가장 명백한 이유는, 메타프로그래밍의 경우 실제 프로그램이 시작되기 전에 최대한 많은 일을 해 둠으로써 더 빠른 프로그램을 얻게 된다는 데 있다. YACC가 문법을 컴파일하기 위해서는 상당한 양의 파싱 테이블 생성과 최적화 단계들을 수행해야 한다. 그런 일들을 실행시점에서 수행한다면 전반적인 프로그램의 성능이 눈에띄게 떨어질 수 있다. Boost.Spirit에는 그런 단점이 없다. 이진수 변환의 경우라면, binary::value는 컴파일 상수가 되고, 컴파일러는 그 값을 직접 목적 코드에 집어넣을 수 있으며, 그래서 실행시점에서는 메모리 참조 없이 그 값을 직접 사용할 수 있게 된다.

그보다는 명백하지 않지만 좀 더 중요한 한 가지 장점은, 계산의 결과가 대상 언어와 좀 더 밀접하게 상호작용할 수 있다는 점이다. 예를 들어서, 표준에 따르면 C++ 배열의 크기는 오직 binary::value 같은 컴파일 시점 상수로만 지정할 수 있다. 실행시점 함수의 반환값으로는 불가능하다. YACC 문법에서, 중괄호로 감싸인 행동(생성된 파서의 일부로 실행된다)으로는 어떠한 C++ 코드도 사용할 수 있다. 그런데 그런 것이 가능한 것은, 그 행동들이 문법 컴파일 도중에 처리되어서 적절히 대상 C/C++ 컴파일러에게 전달되기 때문이다.

Boost.Spirit의 경우에는 파서 문법 자체가 C++ 코드이므로 그런 중간적인 변환 단계가 필요없다.

1.6.2 대안 2 : 사용자 해석

계산을 실행시점이나 컴파일 시점에서 수행하는 대신, 그냥 프로그래머가 직접 처리할 수도 있다. 사실 이진수를 C++ 리터럴 상수로 사용할 수 있는 16진수 형태로 변환하는 작업은 프로그래머들이 흔히 하는 일이고, 또한 YACC와 Boost.Spirit가 문법 명세를 파서로 변환하기 위해 수행하는 번역 단계들 역시 사람이 직접 수행할 수 있을 정도로 잘 알려져 있다. 메타프로그램을 작성해서 단 한 번 정도만 사용하는 경우라면, 그냥 사용자 해석 쪽이 더 간편할 것이다. 사실 이진수를 변환하는 정확한 메타프로그램을 작성하는 것보다는 사람이직접 이진수를 변환하는 게 더 쉬운 일이다. 그러나 그런 일이 빈번하게 일어난다면 메타프로그램을 하나 만들어서 계속 사용하는 편이 더 편리하다. 게다가, 그런 메타프로그램을 다른 프로그래머들과 공유한다면 효과는 더욱 커진다.

그리고 그 사용 횟수와는 무관하게, 메타프로그램은 좀 더 표현력 있는 코드를 만들 수 있게한다. 왜냐하면 계산 결과를 프로그래머의 머리 속에 있는 모형과 잘 일치하는 형태로 표현할 수 있기 때문이다. binary의 경우, 이진수의 비트 하나하나가 의미가 있는 상황이라면 42나 0x2a보다는 binary<101010>::value가 훨씬 더 이해하기 쉽다. 마찬가지로, 손으로 직접 작성한 파서의 C 소스는 그 문법 요소들 사이의 논리적인 관계를 애매하게 만들 정도로복잡한 경우가 많다.

마지막으로, 사람은 누구나 실수를 하기 마련인데, 메타프로그램의 로직은 단 한 번만 작성하면 되므로, 그 결과로 생긴 프로그램은 정확성과 유지보수성 면에서 더 우월할 가능성이 크다. 이진수를 변환하는 것은 너무 쉬운 일이라서 크게 주의를 기울이지 않을 수 있으며, 그래서 실수를 하기가 쉽다. 반면, 해본 사람은 알겠지만, 파싱 테이블을 손으로 직접 작성하는 작업은 대단히 신경 쓰이는 일이다. 따라서 실수를 방지할 수 있다는 점만으로도 YACC같은 파서 생성기를 사용할 충분한 이유가 된다.

1.6.3 C++ 메타프로그래밍이 필요한 이유

메타프로그래밍은 그 영역 언어가 프로그램 나머지 부분에 쓰이는 주 언어의 부분집합인 C++ 같은 언어에서 더욱 강력하고 편리하다. 이유를 들자면:

▣ 사용자는 새로운 문법을 배우지 않고도, 그리고 코드의 흐름을 깨지 않고도 영역 언어에 직접진입할 수 있다.

▣ 다른 코드 (특히 다른 메타프로그램)와 메타코드의 연동이 훨씬 더 매끄럽다.

▣ YACC에 쓰이는 것 같은 추가적인 빌드 단계가 필요하지 않다.

전통적인 프로그래밍에서, 우리는 표현력, 정확성, 효율성 사이의 균형을 맞추려고 노력을 하곤한다. 메타프로그래밍을 이용하면, 표현력과 정확성에 필요한 계산을 실행시점에서 컴파일 시점으로 옮김으로써 그러한 균형에서 나타나는 긴장을 단절시킬 수 있는 경우가 많다.

1.7 언제 메타프로그래밍을 할 것인가

지금까지 템플릿 메타프로그래밍이 왜 필요한지에 대한 몇 가지 예제들을 살펴보았으며, 템플릿 메타프로그래밍을 어떻게 하는 것인지도 아주 조금 맛을 보았다. 그런데 메타프로그래밍이 언제 적합한지에 대해서는 명확하게 이야기하지 않았다. 다만, 템플릿 메타프로그래밍의 이점을 이야기하면서, 템플릿 메타프로그래밍의 사용에 관련된 기준들은 대부분 언급한바 있다. 요약하는 의미에서, 다음 조건들 중 하나라도 해당이 되는 경우라면 메타프로그래밍을 고려할 필요가 있다.

▣ 코드를 문제 영역의 추상들을 통해서 표현하고자 할 때. 예를 들어 파서를 수많은 숫자들로 채워진 테이블들과 여러 서브루틴들의 집합으로 표현하는 대신, 정규적인 문법 형태로 표현하려는 경우라던가, 배열 산술을 숫자열들에 대한 복잡한 루프 대신 행렬과 벡터에 대한 연산자 표기를 통해서 표현하려는 경우 등.

▣ 메타프로그래밍을 사용하지 않는다면 상당한 양의 형판 (boilerplate) 구현 코드를 작성해야 하는 경우.

▣ 구성요소의 구현들을 그 구성요소의 형식 매개변수들의 속성에 기반해서 선택해야 하는 경우.

▣ 정적 형식 점검과 행동 커스텀화 같은 C++의 일반적 프로그래밍 (generic programming)의 가치있는 속성들을 효율성을 잃지 않고 활용하고자 하는 경우.

▣ 모든 것을 외부 도구나 커스텀 소스 코드 생성기 없이 C++ 언어로 해결하고자 하는 경우.

1.8 메타프로그래밍 라이브러리가 필요한 이유

이 책에서는 무에서부터 출발해 메타프로그램을 작성하는 대신, Boost Metaprogramming Library(MPL)가 제공하는 고수준 편의 수단들을 이용한다. 독자가 MPL을 배우려고 이 책을 고른 것은 아니라고 해도, MPL을 배워두면 두고두고 유용할 것이다. 그 이유를 들자면:

  1. 품질. 템플릿 메타프로그래밍 구성요소들을 사용하는 프로그래머들은 그런 구성요소들을 어떤 좀 더 커다란 목표에 적용하는 구현 세부로 간주한다(그런 프로그래머들의 목적에서 본다면 잘못된 관점은 아니다). 그러나 MPL 저자들은 유용한, 고품질의 도구들과 관용구들로서의 템플릿 메타프로그래밍 구성요소 개발 자체를 자신의 주된 임무로 간주한다. 일반적으로, Boost MPL의 구성요소들은 개별 프로그래머가 다른 어떤 목표를 만족하려는 과정에서 만들어낸 것들보다 더 유연하며 더 잘 구현된 것이다. 게다가, 라이브러리가 갱신됨에 따라 구성요소들이 좀 더 최적화되고 개선될 것으로 기대할 수 있다.

  2. 재사용. 모든 라이브러리들은 코드를 재사용할 수 있는 구성요소들로 캡슐화한다. 좀 더 중요하게는, 잘 설계된 일반적 라이브러리는 문제에 접근할 때 재사용 가능한 정신적 모형을 제공하는 개념들과 관용구들의 틀(프레임웍)을 확립한다. C++ 표준 라이브러리가 반복자와 함수 객체 프로토콜을 제공하는 것처럼, Boost Metaprogramming Library는 형식 반복자와 메타함수 프로토콜을 제공한다. 잘 설계된 관용구들의 프레임웍은 메타프로그래머의 설계 결정들에 초점을 두며, 그럼으로써 프로그래머가 눈앞의 과제에 좀 더 집중할 수 있게 해준다.

  3. 이식성. 좋은 라이브러리는 플랫폼마다 차이가 존재한다는 추한 현실을 좀 더 잘 극복할 수 있게 한다. 이론적으로 C++ 메타프로그램에는 플랫폼 차이라는 것이 영향을 미치지 않아야 하지만, 현실적으로는 그렇지 못하다. 그러한 차이는 표준이 제정된 후 6년이 지난 지금에도 컴파일러들의 템플릿 지원에 여전히 남아있다. 사실 C++ 템플릿은 C++ 언어에서 가장 진보된 기능인 동시에 복잡한 기능이라서 그럴 만도 하겠지만, 어쨌든 C++의 메타프로그래밍은 템플릿에 의존하기 때문에 플랫폼 차이는 여전히 중요한 문제이다.

  4. 재미. 같은 틀의 코드를 수없이 반복하는 것은 지루한 일이다. 반면, 고수준 구성요소들을 읽기 쉽고 우아한 설계로 조합하는 것은 재미있는 일이다. MPL은 가장 자주 반복되는 메타프로그래밍 패턴들에 대한 필요성을 제거함으로써 지루함을 줄여준다. 특히, MPL을 이용하면 종료 조건을 위한 특수화와 명시적인 재귀를 쉽게, 그리고 우아하게 피할 수 있는 경우가 많다.

  5. 생산성. 프로그래머가 재미있게 프로그래밍하는가의 문제는 개인적인 만족감뿐만 아니라 프로젝트의 성공에도 큰 요인이 된다. 재미가 없으면 코딩은 지루해지고, 점점 느려지고, 무성의해진다. 그리고 버그를 가진 코드는 느리게 작성한 코드보다 훨씬 더 비싸다. 이러한 점들은 사실 다른 모든 라이브러리들의 개발에서 현실적으로 고려하는 사항들이다. MPL 같은 라이브러리가 나왔다는 것은, 템플릿 메타프로그래밍이 비밀스러운 영역에서 벗어나서 모든 C++ 프로그래머들의 일상의 수단이 되어가고 있다는 증거라 할 수 있다.

마지막으로, 네 번째의 재미 라는 항목을 강조하고 싶다. MPL은 메타프로그래밍을 단지 쉽고 실용적인 것으로 만들어주는 데서 그치는 것이 아니라, 좀 더 재미있는 것으로 만들어 주기까지 한다. 우리가 MPL을 사용하고 개발하면서 느꼈던 재미를 독자 역시 느낄 수 있길 바란다.

unsigned const five = binary<101>::value ;
unsigned const seven = binary<111>::value ;
unsigned const nine = binary<1001>::value ;
반응형

'3.구현 > C or C++' 카테고리의 다른 글

STL에서 문자열 트림 (string trim)하기  (0) 2008.08.13
함수호출 규약  (0) 2007.06.18
[패턴] Command  (0) 2007.02.16
Vectors in STL  (0) 2007.02.16
블록 메모리 복사 성능시험  (0) 2007.02.16