원문 : C++ Modifier Types


주의 : 번역이 개판이므로 원문을 참조하세요. 

주의 : 허락받고 번역한 것이 아니기 때문에 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해 잘 알려진 용어나 발음이 비슷한 용어는 한글 표기합니다.


C++ Modifier Types


C++ 은 char, int, double data type 앞에 modifier 를 가질 수 있도록 허용한다. Modifier 는 base type 의 의미를 수정하는데 사용되며, 그래서 다양한 상황에 대한 요구를 더 정확하게 만족시킨다.


그 data type modifier 의 리스트는 아래와 같다 :

  • signed
  • unsigned
  • long
  • short


signed, unsigned, long, short 는 integer base type 들에 적용될 수 있다. 부가적으로 signed 와 unsigned 는 char 에 적용될 수 있으며, long 은 double 에 적용될 수 있다.


signed 와 unsigned 는 long 이나 short 앞에 사용될 수도 있다. 예를 들면 unsigned long int 이다.


C++ 은 unsigned, short, long 의 integer 를 선언하기 위한 단축 표기를 허용한다. 당신은 int 없이 단순히 unsigned, short, long 만을 사용할 수 있다. int 는 내포되어 있다. 예를 들어 다음의 두 문장은 둘 다 unsigned integer 변수를 선언한다.



signed 와 unsigned 가 C++ 에 의해 해석되는 방식의 차이를 이해하기 위해서는, 다음과 같은 짧은 프로그램을 실행해 보면 된다 :



프로그램이 실행되면, 출력은 다음과 같다.


-15536 50000


위의 결과는 50,000 을 short integer 로서 표현하는 bit 패턴이 short 에 의해 -15,536 으로 해석되기 때문이다.


Type Qualifier in C++


Type qualifier 는 변수 앞에서 부가적인 정보를 제공한다.


 Qualifier   Meaning 
 const 

 const type 의 개체는 프로그램 실행 동안에 변경될 수 없다.

 volatile

 volatile 은 컴파일러에게 변수의 값이 프로그램에 의해서 명시적으로 지정되지 않은 방식으로 변경될 수 있음을 통보한다.

 restrict

 restrict 에 의한 pointer qualifier 는 초기에 단지 그것이 가리키는 개체에 의해서만 접근 가능하다는 것을 의미한다. C99 에서만  restrict 라 불리는 새로운 유형의 qualifiler 가 추가되었다.




volatile 과 restrict 는 정말 잘 안 쓰이는 keyword 이다. 자세한 내용은 다음 링크들을 참조하기 바란다.


C/C++ volatile 키워드.

C/C++ volatile 에 대한 오해.

[ C/C++ ] volatile 선언의 의미에 대한 정리.

restrict, WIKIPEDIA.


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 2-6* :


처음에는 Boost 형식 특질 라이브러리가 제공하는 형식 산술이 완벽해 보이겠지만, 사실은 그렇지 않다. 그 라이브러리의 수단들이 포괄하지 않은 형식 범주들과 관계들, 변환들이 적어도 몇 개는 존재한다. 예를 들어, 그 라이브러리는 부호 있는 정수 형식에 대한 부호 없는 버전의 정수 형식을 얻을 수 있는 수단을 제공하지 않는다. 그런 빠진 부분들을 최대한 많이 찾아 볼 것. 각 특질 범주마다 적어도 하나는 있으며, 전체적으로 적어도 11 개는 있다. 빠진 특질들 각각의 인터페이스를 설계하고, 그런 특질이 필요함을 보여 주는 용례도 제시하라.




풀이 :


일단 이 문제를 풀기 위해서는 "형식 산술" 이 의미하는 바에 대해서 이해할 필요가 있다. 책에서는 "형식 산술" 을 다음과 같이 정의하고 있다.


[ 표 2.5 ] 에 나와 있는 메타 함수들은 근본적인 형식 계산들을 수행하는 메타 함수들이다. 지금까지 살펴 본 다른 형식 특질 메타 함수들과는 달리, 이들은 부울 값이 아니라 형식을 계산 결과로 낸다. 이들을 "형식 산술" 의 연산자들이라고 생각하면 될 것이다.


- C++ Template Metaprogramming, 2.7 메타함수의 정의, 48 페이지.


형식 산술을 위한 메타 함수의 리스트는 다음과 같다.



위의 형식 산술 연산자들은 modifier 나 qualifier 를 붙였다가 떼는 역할을 수행하고 있다. 일단 modifier 와 qualifier 의 개념에 대해서 알고 있어야 어떤 것이 가능하고 어떤 것이 불가능한지 판단할 수 있을 것이다. 다음 링크를 참조하라 : [ 번역 ]C++ Modifier Types.


위의 boost 메타 함수들은 modifier 와 관련한 형식 산술 연산자를 제공하고 있지 않다는 것을 알 수 있다.


1. add_unsigned< T >


unsigned modifier 를 추가한다.


용례 : 



이것을 직접 구현하려고 했을 때 일반적인 template 구현으로는 구현이 불가했다. 다음과 같은 template 은 에러를 발생시킨다.



const  같은 qualifier 는 "const T" 와 같은 부분 특수화가 가능했지만, 일반적인 modifier 는 그것이 불가능했다. 아마도 modifier 들은 정수형의 기본형에 대해서만 성립하는 것이기 때문에 그런 것이 아닌가 싶다. 예를 들어 unsigned std::vector 같은 표현은 성립하지 않는다. 혹은 [ 번역 ]C++ Modifier Types 에서도 언급하고 있듯이 unsigned 자체가 단축표기로 하나의 형을 나타내기 때문이 아닌가 싶다. 이 경우에는 add_unsigned< unsigned int + T > 의 형태가 되는게 아닌가 싶다.


어쨌든 이를 구현하기 위해서는 다음과 같이 모든 기본형에 대해서 부분 특수화를 하는 수밖에 없다. 아래는 int 에 대해서 부분 특수화를 한 예이다.



2. remove_unsigned< T >


unsigned modifier 를 제거한다.


용례 :



3. add_signed< T >


signed modifier 를 추가한다.


4. remove_signed< T >


singed modifier 를 제거한다.


5. add_long< T >


long modifier 를 추가한다.


6. remove_long< T >


long modifier 를 제거한다.


7. add_short< T >


short modifier 를 추가한다.


8. remove_short< T >


short modifier 를 제거한다.

















주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 4-5 :


다음 함수 템플릿은 std::find 에 대한 "컨테이너 기반" ( 반복자 기반이 아니라 ) 인터페이스를 제공하기 위한 것이다.



현재 코드에서, container_find 는 const 컨테이너들에 대해서는 동작하지 않는다. 코드는 std::find 가 돌려 주는 const_iterator 를 Container::iterator 로 변환하려 하는데, 임의의 컨테이너 형식 x 에 대해 Container 는 const x 가 되기 때문에 컴파일이 실패한다. 이 문제를, container_find 의 반환 형식을 계산하는 작은 메타프로그램을 작성하여 해결하라.




풀이 :


이 코드를 보고 "Container& 인데 왠 const 컨테이너가 들어 온다는 거지?" 라는 의문을 가질 수 있다. 하지만 저건 그냥 type 이 아니라 template 매개 변수라는 것에 주목해야 한다. Container 자체에 예를 들면 const vector< int > 로 선언된 것이 들어 올 수 있다.


일단 에러가 나는 상황을 재현해 보자. 



이 문제를 해결하는 것은 간단하다. 다음과 같은 메타 함수를 하나 만들어서 반환값이 들어갈 부분에 넣어 주면 된다. 의미를 이해하는 것은 크게 어렵지 않을 것이다.



여기에서 특이한 것은 mpl::identity 를 사용한다는 것이다. mpl::eval_if 는 말 그대로 메타 함수를 평가하는 것이기 때문에 mpl::identity 를 사용하지 않으면 그냥 return 값이 make_iterator< Container > 가 되어 버린다. make_iterator 를 메타 함수로서 실행해 주기 위해서는 mpl::identity 를 상속한 형태가 되어야 하며, 최종적으로는 make_iterator< Container >::type 을 실행함으로써  Container::const_iterator 나 Container::iterator 가 반환된다. make_iterator 가 결국은 mpl::identity 를 상속하게 되기 때문에 mpl::identity::type 을 실행한 것과 동일한 효과를 내는 것이다.


이해를 돕기 위해서 mpl::eval_if 가 실행되고 나서의 결과를 단순하게 표현한다면 다음과 같다.



전체 코드는 다음과 같다.





다른 사람의 구현도 살펴 봤는데 필자와 다르지 않았다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_4-5


원문 : C++ Operator Overloading Tutorial, Function Call Operator ()


주의 : 번역이 개판이므로 이상하면 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해서 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.


Function Call Operator ()


C++ 에서 함수 호출 연산자는 () 연산자이다. 이것은 한 가지 중요한 차이점을 제외하고는 이전에 설명한 subscript 연산자( 역주 : [] )와 유사하게 중복정의된다( overloaded ). subscript 연산자는 단지 하나의 인자만을 받는 반면에, 함수 호출 연산자는 다양한 형식의 여러 인자들을 받을 수 있다. 이러한 특별한 사실은 우리에게 매우 중요하다.


예를 들어 우리가 2 차원 배열 클래스를 가지고 있다면 우리는 배열의 특정 cell 을 참조하기 위해서 [] 를 중복정의할 수 없다. 왜냐하면 [] 는 인자로 하나의 값만을 취하기 때문이다. 그러나 우리는 () 연산자를 중복정의할 수 있다. 왜냐하면 그것은 다중의 인자들을 취할 수 있으며, 즉 우리는 인자로 row 와 column 을 지정할 수 있다. 사실 그 함수 호출 연산자는 단지 다양한 형식의 가변 개수의 인자들을 취할 수 있는 중복정의된 연산자일 뿐이다. 이는 우리가 다양한 상황에서 그것을 사용( 혹은 잘 못 사용)할 수 있게 해 준다.


우리의 예제를 위해, 우리는 () 연산자가 [] 연산자와 동일한 일을 수행하도록 중복정의하고 이전 예제를 빌드할 것이다. 공교롭게도 어떤 언어들( BASIC 이나 FORTRAN )은 () 를 배열 subscripting 을 위해 사용한다. 그래서 사람들은 그러한 방식으로 사용되는 () 을 보는데 익숙하다. 우리는 클래스에 다른 멤버 함수를 추가함으로써 작업을 시작한다.



이 함수 구현은 이전 페이지의 것과 매우 유사하다.



이전처럼 우리는 이 개체를 다음과 같이 검사할 수 있다 :



이 코드를 실행하면 원하는 결과를 산출한다.


이전 페이지( subscript operator )에서 보았듯이, 항상 참조를 반환할 필요는 없다. 예를 들어 당신이 개체를 읽기 전용으로 만들기 원한다면, int& 참조 대신에 const int 를 반환하도록 만들 수 있다.


Excercises


1. 우리의 중복 정의된 연산자는 배열 범위 내에 index 가 있는지 검사하지 않는다.  필요하면 예외를 던지도록 코드를 추가하라. 예외를 던지는 대신에 필요하면 배열 크기를 변경할 수 있는 다른 버전도 만들어 보라.


2. 컴파일러에 의해 생성되는 기본 복사 생성자는 문제가 있다. 그것은 개체의 인스턴스로부터 다른 인스턴스로 데이터를 복사하는 대신에 포인터를 복사한다. 두 개의 포인터가 같은 메모리를 가지고 있기 때문에 하나가 파괴가 될 때 문제가 발생한다. 다른 인스턴스의 포인터는 해제된 메모리를 가리키게 된다. 해결책은 자신만의 복사 생성자를 작성해서 적절하게 데이터의 복사본을 만드는 것이다. 적절하게 데이터를 다루기 위한 복사 생성자를 작성하라.


3. 2 차원 배열을 다루기 위해 클래스를 재작성하라. 다음 코드는 당신이 작업을 시작하는데 도움을 줄 것이다.



아래 코드로 검증하라.



출처 : MSDN, explicit( C++ )


주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 가독성을 높이기 위해, 잘 알려진 용어나 발음이 비슷한 용어는 한글로 표기합니다.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.


explicit ( C++ )


하나의 매개변수만을 가지는 C++ 생성자는 자동적으로 묵시적( implicit ) 형변환을 수행한다. 예를 들어, 생성자가 문자열 포인터를 기대하고 있을 때 당신이 int 를 넘겨준다면, 컴파일러는 int 를 문자열 포인터로 변환해야만 한다는 코드를 추가할 것이다. 그러나 당신이 이러한 자동적인 행위를 항상 원하는 것은 아닐 것이다.


당신은 생성자 선언에 explicit 를 추가하여 묵시적인 변환을 방지할 수 있다. 이는 그 코드를 올바른 형식의 매개변수를 사용하거나 올바른 형식으로 매개변수를 변환하는 것을 강제한다. 즉 그 변환이 명시적으로( visibly ) 코드에 표현되어 있지 않다면, 에러가 발생할 것이다.


explicit 키워드는 명시적으로 개체를 생성하기 위해서 in-class 생성자 선언에만 적용될 수 있다.


Example


다음 프로그램을 컴파일하는 것은 에러를 산출한다. 이 코드는 묵시적 형변환을 수행하려고 하지만, explicit 키워드의 사용이 그것을 방지한다. 이 에러를 해결하기 위해서는 explicit 키워드를 제거하고 g 의 코드를 적용하라.



주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 4-4 :


정수 연산자들과 형식 특질 라이브러리의 수단을 이용해서 다음의 합성 특질들을 구현하라.


is_data_member_pointer

is_pointer_to_function

is_reference_to_function_pointer

is_reference_to_non_const




풀이 :


이 문제는 논리 연산자들을 적절하게 사용해 메타 함수를 합성하는 방법을 알고 있는지 확인하는 문제이다.


일단 함수를 합성하기 위해서는 문제에 나온 함수를 각각의 ( 함수 ) 요소로 분해할 수 있어야 한다.


1. is_data_member_pointer


이 메타 함수는 다음과 같이 분해된다.


is_data_member_pointer = is_member_pointer && !( is_member_function_pointer )



그리고 나서 이 함수를 테스트하는데 사용할 class 를 정의해 보자.



그런데 여기에서 문제가 발생한다. 우리가 만든 메타 함수는 매개변수로 type 을 받아 들이므로 우리가 테스트하고 싶어하는 field 의 형식에 어떻게 접근해야 할지 알 수 없다는 것이다. 예를 들어 다음과 같이 테스트하는 것은 오류이다.



그래서 C++ 0x 부터 추가된 decltype 을 사용해 봤다. 그런데 다음과 같은 결과가 나온다.



결국 어떻게 테스트해야 하는지 알수 없어서 CPPTM Answers - Exercise 4-4 를 참조했다. 여기에서는 빈 클래스인 foo 를 선언하고, typedef 를 통해서 새로운 type 을 선언했다. 일단은 그렇게 테스트를 해 봤다.



나중에 member variable 에서 class 까지 포함하는 type 을 획득할 수 있는 방법에 대해서 알아 봐야 겠다.


2. is_pointer_to_function


이 문제는 함수 포인터의 본질에 대해서 얼마나 이해하고 있느냐를 확인하고 있다. 처음에는 다음과 같이 생각했다.


is_pointer_to_function = is_function || is_member_function_pointer


그런데 "왜 is_function 은 is_function_pointer 가 아닌 것일까?" 라는 의문이 들기 시작했고, 다른 사람의 구현을 살펴 보았다. 


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_4-4



사실 함수포인터는 그냥 함수라고 생각했었는데, 지금까지 아주 잘못된 생각을 하고 있다는 것을 알았다. 일반적인 변수와 유사하게 함수 자체도 변수처럼 동작하는 것이었다. 그래서 여기에서 함수 포인터에 대해 다시 짚어 볼 필요가 있었다. 다음은 함수 포인터의 예이다. 다음 링크에서 가지고 온 예이다 : Program as Data: Function Pointers



위의 예에서 마치 변수의 주소를 구하듯이 함수의 주소를 구하는 것을 볼 수 있다. 변수는 어떤 메모리 공간에 대한 이름이다. 그리고 그 이름은 어떠한 주소로 매핑된다. 위에서도 언급했듯이 변수와 마찬가지로 함수는 어떤 로직을 담고 있는 메모리 공간에 대한 이름이 되겠다. 그러므로 이것도 참조하거나 포인팅하는 것이 가능한 것이다. 특이한 점은 그 포인터 자체에 () 연산자를 걸어서 호출이 가능하다는 것이다. () 연산자에 대해서는 다음 글을 참조하기 바란다 : [ 번역 ] Function Call Operator ().


그런데 위의 구현은 한 가지 문제가 있다. mpl::and_ 의 두 번째 매개 변수에서 ::type 을 지정함으로써 지연 평가가 이루어지지 않는다는 것이다. 이를 지연 평가가 가능하게 고쳐 보려고 했는데 잘 되지 않았다. 이 부분도 연구해 볼 필요가 있다.


어쨌든 is_pointer_to_function 은 다음과 같은 표현이라고 할 수 있다.


is_pointer_to_function = is_pointer && ( is_function( remove_pointer ) )


그리고 위의 구현은 불필요하게 and_ 를 호출하고 있기 때문에 최종적으로는 다음과 같이 구현할 수 있다.



3. is_reference_to_function_pointer


점점 복잡해 지고 있다. 하지만 function 에 대해서 이해한 상황이기 때문에 그리 어려울 것은 없다. 앞에서 만들었던 is_pointer_to_function 을 재사용하고, boost::remove_reference 로 reference 를 제거해 주기만 하면 된다.


is_reference_to_function_pointer = is_reference && ( is_pointer_to_function( remove_reference ) )


코드는 다음과 같다.



4. is_reference_to_non_const


이 문제는 비교적 쉬운데 다음과 같이 표현할 수 있다.


is_reference_to_non_const = is_reference && !( is_const( remove_reference ) )


코드는 다음과 같다.



여기에서 의문을 가지는 사람이 있을 것이다. 왜 is_const 에서 remove_reference 를 해야 하는 것일까? 필자가 다음과 같이 test 했을 때 error 를 냈다.



아직까지 그 이유는 정확하게 모르겠지만 추측해 보면, boost 의 type traits 는 특정 순서대로 한정자를 제거해 가면서 type check 를 하도록 만들어져 있는 것 같다.


주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 4-3 :


다음 코드에서 불필요한 인스턴스화를 제거하라.



수정된 메타함수들의 의미론이 수정되기 전과 동일한지를 점검하는 테스트들도 작성하라.




풀이 :


위의 문제들은 게으른 평가에 대한 이해도를 확인하는 문제이다. ::type 이나 ::value 가 불리는 순간 해당 템플릿은 인스턴스화된다. 그러므로 게으른 평가를 위해서는 메타함수들이 실제로 평가되지 않도록 ::type 이나 ::value 를 없애야만 한다. 물론 반드시 필요한데 호출하는 코드를 지워버린다면 에러가 나므로 주의해야 한다.


1. next_if 메타 함수.


일단 next_if 의 의미를 파악하자.


N 을 Predicate 함수에 적용한 결과가 true 이면

    mpl::next< N > 을 호출한 결과를 반환하고

그렇지 않으면

   N 을 그대로 반환한다.


mpl::next 는 다음과 같은 기능을 가지고 있다.


순차열 안의 다음 반복자( iterator )를 반환한다. [ 주의 : next 는 여러 개의 중복된 의미를 가지는데, 그것의 인자의 형식에 따라 다르다. 예를 들어 X 가 정수형 상수라면, next< X > 는 같은 형식의 증가된 정수형 상수를 반환한다. 이후의 명세는 iterator-specific 하다. 대안으로 선택할 수 있는 구문에 대한 세부적인 설명을 원한다면 관련 개념들의 문서를 참조하기 바란다. ]

http://www.boost.org/doc/libs/1_52_0/libs/mpl/doc/refmanual/next.html


'predicate' 는 '술어' 라는 뜻이기 때문에 mpl::if_ 의 조건을 결정하기 위한 서술문을 의미하는 메타 함수를 의미한다고 보면 될 것이다. 예를 들어 C++ 식으로 표현하면 다음과 같다; if( true == Predicate( N ) ){ ... }.


이를 테스트하기 위해서는 Predicate 를 평가해서 나온 ::type 이 가리키는 형식이 반드시 ::value 라는 멤버를 가지고 있어야만 한다. 그래서 다음과 같은 식으로 평가를 해 볼 수 있다. Predicate 의 형태를 보요 주기 위해서 메타 함수 클래스를 사용한 경우와 자리표 표현식을 사용한 경우의 두 가지 예제를 모두 보여 주겠다.



자 이제 이것을 게으른 평가가되도록 최적화해 보자.


현재 mpl::apply 메타 함수의 ::type 과 mpl::next 메타 함수의 ::type 이 불리고 있다. 책에서 배운 대로 mpl::identity 를 사용해서 제거해 보도록 하자.



안타깝게도 우리의 기대를 무참하게 짓밟아 버린다. 왜일까?


mpl::identity::type 은 그것의 매개변수로 들어 왔던 스칼라나 메타함수의 원형을 그대로 돌려 준다. 그러므로 아직까지는 mpl::identity< mpl::next< N > > 과 mpl::next< N > 이 평가되지 않은 상황이다. 이를 해결하기 위해서는 next_if2 : mpl::if_< ... >::type 으로 만들어 주거나 사용할 때 next_if2< ... >::type::type::value 를 통해 값을 가져와야 한다. 사용자들은 보통 ::type 을 한 번만 사용한다고 인지하고 있기 때문에 필자는 구조체 쪽에서 처리하는 방법을 택했다. 최종 코드는 아래와 같다.



사실 mpl::eval_if 를 사용하면 mpl::identity 를 사용할 필요가 없다. 물론 다들 알고 있겠지만 책에서 배운 내용을 점검해 보는 차원에서 위의 방식으로 풀어 보았다. 실제로 아래와 같이 매우 깔끔한 코드를 만들 수 있다.



2. formula 메타 함수.


mpl::not_equal_to 는 integral constant 에 대해서 적용되므로 N1 과 N2 가 integral constant 라는 것을 알 수 있다. 그러므로 formula 메타 함수는 다음과 같은 기능을 가지고 있다.


IF N1 과 N2 가 다르면

IF N1 이 N2 보다 크면

N1 에서 N2 를 뺀 결과 반환.

ELSE

N1 을 반환.

ELSE

N1 에 N1 에다 2를 더한 결과를 더한 결과 반환.


조금 복잡하기는 하지만 1 번과 크게 다를 것 없다. 일단 mpl::if_ 를 mpl::eval_if 로 바꾼다. 그리고 나서 mpl::eval_if 내의 메타 함수들에서 불필요한 ::type 을 제거한다.



주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 4-2 :


실습 과제 4-1 의 logical_or 와 logical_and 메타 함수들의 구현을, 인수들을 최대 5 개까지 받을 수 있도록 확장하라.




풀이 :


이 문제를 해결하기 위해서는 가변 개수의 매개변수들을 받을 수 있는 구조를 만들어야 한다. 


하지만 안타깝게도 템플릿에 가변 개수의 매개변수를 받을 수 있는 방법은 없다. 일단은 5 개의 매개변수를 받을 수 있는 logical_or 와 logical_and 를 만드는 데서부터 시작하자.


 

그런데 ( 2 개 이상인 ) 가변 개수의 매개변수를 받기 위해서는 어떻게 해야 할까? 이를 위해서는 템플릿 매개변수에 initializer 를 설정하는 방법이 있다. 사용자가 매개변수를 입력하지 않더라도 자동으로 기본값이 들어 가도록 하는 방법이다. none_ 이라는 구조체를 기본값으로 설정하고, 만약 그 구조체가 입력되면 연산을 종료한다고 가정했다.



이제 logical_and_impl 구현해야 하는데, 이것을  인자가 5 개인 것부터 2 개인 것까지 구현을 한다. 그리고 logical_and_impl 가 다음 매개변수들을 위해 실행되어야 할지를 판단하기 위해 mpl::eval_if 를 사용한다. 다음 매개변수들을 위해 실행되지 않는 경우는 두 가지이다; 현재 조건이 false 일 경우와 매개변수가 none_ 일 경우이다.


매개변수가 none_ 일 경우를 위해서 부분특수화를 구현했다. 그리고 mpl::eval_if 는 게으른 평가를 수행하므로 4-1 의 문제가 제시한 조건을 만족한다.


다음은 소스 코드이다. 그런데 logical_or 는 귀차니즘 때문에 구현하지 않았다. 비슷한 방식으로 구현하면 된다.



다른 사람의 구현도 살펴 보자.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_4-2


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 4-1 :


mpl::or_ 와 mpl::and_ 를 본 딴, logical_or 와 logical_and 라는 이름의 이항 메타함수들을 구현하라. 그리고 실습 과제 4-0 의 테스트들로 그 구현을 시험하라.




풀이 :


이 문제는 게으른 평가( lazy evaluation )에 대한 이해를 얼마나 하고 있는지를 확인하는 문제이다. 게으른 평가가 되기 위해서는 ::type 이나 ::value 가 명시적으로 지정되면 안 된다.


필자는 부분 특수화를 통해서 이 문제를 해결했다. 자세한 사항은 주석을 참조하라.



아래 링크에 있는 다른 사람의 구현도 살펴 보기 바란다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_4-1


주의 : 공부하면서 정리하는 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 4-0 :


mpl::or_ 와 mpl::and_ 메타 함수에 대해, 부울 표현식의 평가 단축 행동을 시험하는 테스트를 작성하라.




풀이 :


이 문제는 "평가 단축 행동"의 의미를 이해하고 있는지를 묻고 있다. 책에서는 다음과 같이 표현하고 있다.


내장 연산자 &&, || 과 마찬가지로, mpl::and_ 와 mpl::or_ 는 "평가 단축" 행동을 가진다는 점 역시 알아둘 필요가 있을 것이다.

- C++ Template Metaprogramming, 4.2 정수 래퍼들과 연산들, 91 페이지.


"평가 단축" 이라고 하는 것은 앞의 결과가 false 면 뒤의 결과를 평가하지 않는다는 것이다. 예를 들어 "A && B" 는 둘 다 true 일 때만 true 이므로 && 연산자의 앞의 피연산자인 A 가 false 이면 B 는 볼 필요도 없이 && 의 결과는 false 이다. 이를 통해 평가 비용을 단축할 수 있다.


이를 테스트해 보기 위해서는 bool 을 반환하는 두 개의 메타 함수가 필요하다. 책에서 많이 나오고 있는 boost::is_scalar 를 첫 번째 메타 함수로 만들고, 항상 컴파일 에러를 내는 Error 라는 메타 함수를 만들자. 그리고 나서 mpl::or_ 와 mpl::and_ 의 두 번째 매개변수로 넣는다.



주의 : 공부하면서 정리한 내용이기 때문에 오류가 있을 수 있습니다.


실습 과제 3-7* :


다음 구성물들의 의미론은 무엇일까?



독자의 답에 도달한 단계들을 보이고, 독자의 가정들을 확인하는 테스트 코드를 작성하라. 라이브러리의 행동이 독자의 분석과 일치하는가? 그렇지 않다면, 실패한 테스트들을 분석해서 실제의 표현식들의 의미론을 파악해 볼 것. 그리고 독자의 가정들이 왜 달랐는지, 또 독자가 생각하기에 어떤 행동이 더 합당한지와 그 이유를 말해 보라.




풀이 :


이 역시 쉬운 듯하면서 절대 쉽지 않은 문제이다. 지금까지 배웠던 람다 표현식에 대해서 얼마나 이해하고 있는지를 검사하는 문제이다. 게다가 근거까지 명확하게 대야 하기 때문에 별표가 안 붙을 수 없다.




typedef mpl::lambda< mpl::lambda< _1 > >::type t1;


mpl::lambda 메타 함수는 자리표 표현식을 위한 메타 함수 클래스를 만들어 내는 역할을 하고 있다고 알고 있었다. 그리고 결국에는 메타 함수 자체를 반환한다고 생각했다. 


_1 은 arg< 1 > 이라는 메타 함수 클래스이고 _1 로 평가되기 때문에 첫 번째 lambda 에서 _1 을 반환하고, 두 번째 lambda 에서 다시 _1 을 반환해 t1 은 _1 을 반환한다고 생각했다. 그래서 예상한 결과는 다음과 같았다.



그러나 이는 전혀 평가되지 않는 문장을 만들어 냈다. 아래 에러와 같이 그냥 문자열처럼 해석해 버린다.


error C2338: ( boost::is_same< t1::type, _1 >::value )


생각해 보니 t1 이라는 것 자체가 어떤 메타 함수 클래스를 나타내기는 하지만, 실제로 어떤 형식을 넣어 특수화하지 않으면 아무런 의미도 없겠다는 생각이 들었다. 그래서 다음과 같이 고쳐 봤다. 자리표 표현식으로부터 메타 함수 클래스를 생성해 주는 것이기 때문에 apply 를 호출해 보았다.



위의 코드 역시 평가되지 않았다.


결국 기본부터 다시 생각해 보기로 했다. 


mpl::lambda 의 의미부터 다시 파악해 볼 필요가 있다. mpl::lambda 는 자리표 표현식이나 메타 함수 클래스를 받아서 메타 함수 클래스를 만들어 준다. 만약 boost::add_pointer 라는 메타 함수를 위한 메타 함수 클래스를 만들기 위해서는 다음과 같이 해야 한다.



자 그러면 아무런 메타 함수나 넣어서 호출할 수 있게 해주는 메타 함수를 만들기 위해서는 어떻게 해야 할까? 다음과 같은 형태를 생각할 수 있다.



??? 자리에는 람다 표현식( 자리표 표현식이나 메타 함수 클래스 )이 들어 와야 할 것이다. 메타 함수 자체는 일급 메타 자료가 아니기 때문이다. 그런데 공급될 것이 메타 함수인지 람다 표현식인지 알 수 없기 때문에, 모든 가능성을 생각하면 무엇이 들어 오든 람다 표현식으로 만들어 줄 필요가 있다. 그러므로 다음과 같이 표현할 수 있다.



위 코드는 메타 함수나 람다 표현식을 메타 함수 클래스로 만들어 주는 메타 함수 클래스를 정의한 것이라고 할 수 있다. 이를 실제로 사용해 보도록 하겠다.





typedef mpl::apply< _1, mpl::plus< _1, _2 > >::type t2;


이것은 mpl::apply 에 의해 내부의 mpl::plus 를 평가한 결과를 외부의 mpl::apply 에 적용하는 메타 함수로 보인다. 일반적으로 mpl::plus 에 들어 오는 것은 수치 래퍼일 것이기 때문에 그것의 ::type 은 자기 자신을 반환할 것이다. 결국 이는 mpl::plus 와 동일한 결과를 낼 것이다.



one 과 two 를 mpl::plus 의 _1 과 _2 에 할당하고 나면 mpl::int_< 3 > 이라는 결과가 나올 것이고, 그것이 다시 mpl::apply 의 _1 에 들어 가서 그것을 평가하면 그 자신을 반환한다.




typedef mpl::apply< _1, std::vector< int > >::type t3;


일단 std::vector 가 메타 함수인지를 확인해 봐야 한다. 그런데 std::vector 는 type 이라는 내포된 형식을 가지고 있지 않았다. 그걸 봤을 때 메타 함수는 아니라고 생각했으며, 이를 인스턴스화하면 에러가 날 것이라 생각했다.


하지만 실제로 해 봤을 때 다음과 같은 코드가 유효했다.



그런데 책에 이미 내용이 나와 있었다. 


자리표에 대해 아직 이야기하지 않은 세부사항이 하나 있다. MPL 은 보통의 템플릿들을 메타 프로그램에 통합시키기 쉽게 만들기 위한 하나의 특별한 규칙을 사용한다. 어떤 규칙이냐 하면, 모든 자리표들이 실제 인수들로 치환된 후의 템플릿 특수화 X 에 내포된 ::type 이 존재하지 않는다면, 그 람다의 결과는 X 자체라는 것이다.

- C++ Template MetaProgramming, 3.5.3 람다와 비 메타함수 템플릿, 77 페이지.


역시 책만 읽고 "아 그렇구나" 하고 넘어가면 안 된다는 점을 다시 한 번 느끼게 됐다.




typedef mpl::apply< _1, std::vector< _1 > >::type t4;


이것은 어떤 형식을 받아 vector 를 만들어 주는 메타 함수이다.





typedef mpl::apply< mpl::lambda< _1 >, std::vector< int > >::type t5;


std::vector< int > 을 입력으로 mpl::lambda< _1 > 가 지정하는 메타함수를 실행해 주는 메타함수라고 생각했다. 하지만 실제 해 보니 t5 에는 apply 라는 메타 함수가 존재하지 않고 그냥 std::vector< int > 와 같았다.



좀 이해할 수 없는 결과였다. 그런데 가만히 생각해 보니, 내부에 있는 mpl::lambda< _1 > 이라는 것이 평가되면 그냥 _1 이 되기 때문에 결국에는 t3 와 같은 결과가 되는 것이었다. 




typedef mpl::apply< mpl::lambda< _1 >, std::vector< _1 > >::type t6;


이것 역시 내부의 mpl::lambda< _1 > 이 _1 로 평가되면서 t4 와 같은 결과를 낸다.





typedef mpl::apply< mpl::lambda< _1 >, mpl::plus< _1, _2 > >::type t7;


이것 역시 내부의 mpl::lambda< _1 > 이 _1 로 평가되면서 t2 와 같은 결과를 낸다.





typedef mpl::apply< _1, mpl::lambda< mpl::plus< _1, _2 > > >::type t8;


mpl::lambda< mpl::plus< _1, _2 > > 는 mpl::plus< _1, _2 > 로 평가되고, 이것은 mpl::apply 의 _1 에 그대로 들어 가기 때문에 최종적으로는 mpl::plus< _1, _2 > 만 남는다.





다른 사람의 해석도 살펴 보기 바란다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_3-7

주의 : 공부하면서 정리한 것이므로 오류가 있을 수 있습니다.


실습 과제 3-6 : 


twice 와 같은 기능을 가진 람다 표현식을 작성하라. 힌트 : mpl::apply 는 메타함수임.




풀이 :


mpl::apply 메타 함수는 메타함수를 템플릿 매개 변수로 받아 호출해 주는 예제이다. 예를 들어 boost::add_pointer 는 그냥 메타 함수이기 때문에( 일급 메타 자료가 아니기 때문에 ) 다른 메타 함수의 매개변수로 설정될 수 없다. 이를 가능하게 하기 위해서는 람다 표현식을 만들어야 한다.


람다 표현식은 다음과 같다.


람다 표현식( lambda expression ). 간단히 말하면, 람다 표현식은 호출 가능한 메타자료이다. 어떤 형태이든 호출 가능한 메타자료라는 것이 없으면 고차 메타함수는 불가능하다. 람다 표현식은 두 가지 형태인데, 하나는 메타함수 클래스이고 또 하나는 자리표 표현식이다.

- C++ Template MetaProgramming, 3.6 세부사항, 79 페이지.


책에서 메타함수 클래스를 만드는 것과 메타함수 전달을 하는 법에 대해서 설명을 많이 하고 있다. 여기에서 굳이 이 문제를 내는 이유는 비관입적으로 메타함수들을 호출하는 법을 제대로 알고 있는지에 대해서 확인하는게 아닐까 싶다.


필자같은 경우에는 자리표 표현식을 사용해 이를 람다표현식으로 표현해 보았다.


자리표 표현식( placeholder expression ). 람다 표현식의 한 종류로, 자리표들을 이용해서 즉석의 부분적 메타함수 적용과 메타함수 합성을 가능하게 한다. 이 책 전반에서 나오겠지만, 이러한 기능은 필요한 바로 그 자리에서 기초적인 메타함수들로 거의 모든 종류의 좀 더 복잡한 형식 계산을 구성할 수 있게 한다.

- C++ Template MetaProgramming, 3.6 세부사항, 79 페이지.



다른 사람들은 어떻게 구현하고 있는지 살펴 보기 바란다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_3-6


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 3-5 :


3.1 절의 차원 해석 코드에는 한 가지 문제가 남아 있다. 힌트 : 다음 코드를 시험해 볼 것.



이 문제를 이번 장에 나온 기법들을 이용해서 고쳐 보라.




풀이 :


m * a 가 만들어 내는 차원 정보는 순차열 내의 값은 동일하지만 같은 형식이라고 할 수 없다. 그러므로  + 연산자와 - 연산자에서 복사 생성자를 만들때 했던 것처럼 형식 검사를 해 줘야 한다.


하는 김에 * 연산자와 / 연산자에서 이 장에 나온 자리표 표현식을 사용해 보았다.



주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


"C++ Template MetaProgramming 실습 과제 3-4 풀이" 와 "[ 번역 ] CPPTM Answers - Exercise 3-4" 에도 불구하고 아직도 이해가 안 가는 사람들이 많을 것이라 생각한다. 번역 글에서 논쟁하는 것을 살펴 보고 있어도 뭔가 머리에 쏙 들어 오는 것이 없을 것이다. 그냥 이해가 가는 사람이 있다면 존경하고 싶다.


필자도 그래서 책을 몇 번이나 다시 보고 여러 가지 코드를 테스트해 보았다.


일단 다음과 같은 코드를 컴파일해 보자.



뭔가 오류가 주저리 주저리 나오는 데 우리가 주목할 것은 마지막 "with[ ... ]" 에 나와 있는 내용이다.


1>d:\mywork\templatemetaprogramming\consoleapplication\consoleapplication.cpp(24): error C2039: 'type' : 'boost::mpl::apply<F,T1>'의 멤버가 아닙니다.

1>          with

1>          [

1>              F=twice<int *,int> *,

1>              T1=twice<int *,int>

1>          ]


위의 오류는 처음 apply 가 평가될 때, 즉 내부 twice 가 평가될 때의 오류를 의미하고 있다. twice 메타 함수의 다음 코드 부분이다.



원래는 mpl::apply 가 적용되고 나면 boost::add_pointer< T > 가 실행되면서 그것의 내포된 type 에 접근해야 하는데, 그것이 실패하면서 "boost::mpl::apply< F, T1 > 의 멤버가 아닙니다" 라는 오류를 뱉어 낸다. 왜 그럴까?


"F = twice< int*, int >*" 가 들어 오고 "T1 = twice< int*, int >" 가 들어 왔다. 아무래도 이것만 봐서는 좀 이해가 안 된다.


그래서 자리표 표현식을 다음과 같이 바꿔 호출해 보았다.



다음과 같은 오류를 낸다.


1>d:\mywork\templatemetaprogramming\consoleapplication\consoleapplication.cpp(24): error C2039: 'type' : 'boost::mpl::apply<F,T1>'의 멤버가 아닙니다.

1>          with

1>          [

1>              F=twice<int *,int> *,

1>              T1=int

1>          ]


"T1 = int" 로 평가되고 "F = twice< int*, int >*" 라고 평가된 것을 볼 수 있다. 즉 내부 twice 의 boost::add_pointer 가  메타 함수 클래스로서 평가되는 것이 아니라, 그냥 메타 함수로 평가되어 버렸다는 것을 알 수 있다.


정리하자면 "F = twice< add_pointer_f, int >*" 식으로 들어 와야 하는 것이, boost::add_pointer< int >::type 으로 평가되어 버렸기 때문에 "F = twice< int*, int >*" 가 되어 버린 것이다( boost::add_pointer< int >::type 은 int* 이기 때문이다 ).


그래서 우리가 mpl::lambda 메타 함수를 사용해 이 자리표 표현식을 메타 함수 클래스로 만들어 주지 않으면 컴파일 에러가 나는 것이다.


원문 : http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_3-4

정보 : C++ Template MetaProgramming 실습 과제 3-4 의 풀이를 잘 이해할 수 있는 보충 자료라 생각해 번역해 봅니다. 논쟁이 참 재밌고 도움이 됩니다.

주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해 잘 알려지거나 우리말과 발음이 비슷한 용어는 한글로 표기합니다.


CPPTM Answers - Exercise 3-4







명시적인 lambda 호출이 왜 필요한지 설명해 줄 수 있는 사람이 있나요? 나는 twice 안에 내포된 apply 가 add_pointer 자리표 표현식을 다룬다고 생각했는데, GCC 3.3.3 은 다음과 같이 하면 컴파일 에러를 내네요 :



아는 사람? -- Matt Brecknell




3.3 절은 왜 그것이 동작하지 않는지에 대한 좋은 설명을 제공합니다 - Ariel




Matt : 3.3 절은 책의 이전 절에서의 twice 메타 함수가 자리표 표현식을 직접적으로 사용하지 않는 이유에 대해서 설명합니다. 그러나 그리고 나서 3.3.2 절은 mpl::apply 에 대한 첫 번째 인자는 모든 lambda 표현식이 될 수 있다고 이야기합니다( 자리표로 만들어진 것들을 포함해서 말이죠 ). 당신의 twice 정의는 mpl::apply  를 사용해서 인자로 넘어 오는 메타 함수를 호출하기 때문에, 나는 mpl::lambda 를 사용하지 않고도 자리표 표현식을 사용할 수 있다고 기대했습니다. 예를 들어 나는 다음 코드가 당신의 twice 정의를 사용해 동작할 것이라고 믿습니다( 비록 나는 그 시점에 MPL 이 가능한 컴파일러를 사용하고 있지 않아서 검사해 볼 수 없었지만 말이죠 ) :



I'm currently working on the theory that the problem with my version of the nested invocation of twice might be something to do with the way the outer twice is evaluating placeholders in the inner twice, but I haven't figured it out yet. -Matt




당신의 이전 코드 예제에서, 먼저 안쪽의 twice 를 생각해 보세요. 그리고 두 번째 인자를 생각해 보세요. 그것은 단지 _1 입니다. 그것은 안쪽 twice 에서는 특별한 의미가 없습니다. 그러므로 안쪽 twice 의 결과는 _1**  입니다. 안쪽 twice 는 메타 함수이지, lambda 표현식이 아닙니다. 그러므로 당신은 mpl::apply 를 적용할 수 없습니다. 만약 mpl::lambda 를 boost::add_pointer 에 사용하게 된다면, 안쪽 twice 는 그것을 lambda 표현식을 생성하는데 사용할 것이고, 그 표현식은 mpl::apply 로 넘겨질 수 있습니다. 그리고 결국 바깥쪽 twice 에 넘어 갈 수 있습니다. 적어도 내가 생각하기에는 그것이 작동 방식입니다( 나는 Dave 가 이를 명확하게 설명할 수 있는지 궁금하군요 ) - Ariel




만약 내가 직접적으로 twice<> 를 ::type 과 함께 평가했다면, 나도 안쪽  twice<> 가 _1** 로 평가될 것이라는 데 동의합니다. 그렇지만 나는 그렇게 하지 않았기 때문에, 나는 바깥쪽 twice<> 가 안쪽 twice<> 를 자리표 표현식으로 취급할 것이라고 생각했습니다( 그리고 당신이 제시했듯이 그냥 메타 함수로 취급하지 않을 것이라 생각했습니다 ). 그러므로 나는 안쪽 twice<> 안의 mpl::apply 가 boost::add_pointer< _1 > 을 메타 함수 클래스로 변환하고, 반면에 두 번째 인자( bare _1 )를 바깥쪽 twice<> 에 대해 대체( substitute )하기 위해 남겨둘 것이라 기대했습니다.그것이 내가 _1 을 두 자리에 모두 사용한 이유입니다. 그것들이 서로 다른 문맥에서 서로 다른 것들을 가리키고 있음에도 불구하고 말이죠.  In any case, I don't see why your reasoning would apply to my formulation, without also applying to yours. 무엇보다도 당신은 당신의 버전에서 "_" 자리표를 같은 위치에 가지고 있습니다. 단 하나의 주요한 차이는 당신이 boost::add_pointer< _ > 를 mpl::lambda<> 안에 래핑했다는 것 뿐입니다. 나는 mpl::lambda 가 안쪽 twice<> 로 하여금 바깥쪽 twice<> 를 위한 "lambda 표현식을 생성"하도록 만든다는 것에 대한 당신의 근거에 동의할 수 없습니다 : 만약 안쪽 twice<> 가 자리표 표현식이라면, 그것은 이미 lambda 표현식입니다( 책 3.6 절 ). 명백히 내 근거가 다소 잘못되기는 했지만, 나는 여전히 당신이 제시한 근거들을 이해할 수 없습니다. - Matt




나는 안쪽 twice 가 자리표 표현식이라고 받아들였습니다. 그리고 나서 바깥쪽 twice 는 ( 각 mpl::apply 를 위한 ) 형식을 위해 두 자리표들을 대체합니다. mpl::lambda 를 사용하는 것은 boost::add_pointer 를 위한 자리표가 대체되는 것을 막습니다. 이것이 정확히 어떻게 동작하는 지는 잘 모르겠군요 - Ariel.




twice< twice< add_pointer< _1 >, _1 >, int > 가 작동하지 않는 이유는 다음과 같습니다. 명확한 영역 특수화를 하지 않으면 mpl::apply< twice< add_pointer< _1 >, _1 >, int >::type 은 다음과 같습니다.



이것은 매우 잘못 되었습니다. http://thread.gmane.org/gmane.comp.lib.boost.devel/115924 에서 진행중인 명시적 영역지정( scoping )을 사용하면, 제대로 작동하는 한 줄 표현은 다음과 같습니다.



명확히 이해하는데 도움이 되었으면 합니다. People/Aleksey Gurtovoy.


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 3-4 :


twice 를 그 자신에 대해 사용해서 T 를 T**** 로 변환하라.




풀이 :


처음에 한 구현은 다음과 같다.



하지만 생각해 보니 위와 같이 구현하면 3-3 의 문제를 푸는 것과 다를 것이 없었다. 여기에서 의도하는 것은 twice 라는 메타 함수를 자신의 메타 함수로 사용해 f( f( x ) ) 와 같은 식으로 사용해서 호출하는 방법을 알고 있는 지를 물어 보는 것이라는 생각이 들었다.


즉 다음과 같은 형태여야 한다.



일단 메타 함수를 파라미터로 넘기기 위해서는 lambda 나 placeholder 를 사용해서 이를 일급 메타 함수로 만들어 줄 필요가 있다는 것까지는 쉽게 생각할 수 있었다. 하지만 이 부분부터가 어렵다. 즉 고차 메타 함수에 대한 이해를 제대로 하고 있는지를 확인하는 문제라 생각한다. 만약 제대로 이해하고 있지 못하다면 책을 다시 한 번 살펴 볼 필요가 있다. 필자도 다시 봤다 ㅠㅠ. 특히 "3.3 자리표 다루기" 를 유심히 보기 바란다.


일단 답부터 말하자면 다음과 같이 호출해야 한다.



위의 코드를 좀 더 이해하기 쉽게 분해하면 다음과 같다.



3.3 절에서도 이야기하고 있듯이 boost::add_pointer 는 메타 함수 클래스가 아니다. 메타 함수 클래스는 다음과 같은 정의를 가지고 있기 때문이다.


메타함수 클래스는 공개적으로 접근할 수 있는 apply 라는 이름의 내포된 메타함수를 가진 클래스이다.

- C++ Template Metaprogramming, 3.1.4, 64 페이지.


즉 apply 를 평가하려는 시점에 boost::add_pointer< _1 >::apply 가 존재하지 않으므로 컴파일에 실패하게 된다. 


그러므로 우리는 mpl::lambda 를 사용해 add_pointer 를 메타 함수 클래스로 만들어 줘야만 한다. mpl::lambda 가 메타 함수 클래스를 만들어 주는 원리에 대해서는 mpl::lambda 의 원리에서 다루고 있으니 참조하기 바란다.


이 시점에서 의문을 품는 사람들이 있을 것이다. 필자도 그랬다. 왜 실습과제 3-3 은 메타 함수 클래스가 아닌 add_pointer 를 그냥 호출할 수 있는 것일까? 



그 이유에 대한 의문을 외쿡인들도 가졌던 것 같다. CPPTM Answers - Exercise 3-4 에 보면 거기에 대한 질문과 답변이 있으니 꼭 참조하시기 바란다.




추가 : 필자는 위의 링크를 보고 제대로 이해를 못해 나름대로 답을 찾으려고 노력했고 그 내용을 정리해 보았다. C++ Template MetaProgramming 실습 과제 3-4 풀이 보충을 참고하라.

주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 3-3 :


twice 를 두 번 사용해서 T 를 T**** 로 변환하라.




twice 는 어떠한 메타 함수와 형식을 받아 두 번 실행해 주는 고차 메타 함수이다. twice 에 boost::add_pointer 메타 함수를 넣어 두 번 실행하는 문제이다.



자리표 표현식을 사용해서 고차 메타 함수를 호출할 수 있는지를 확인하는 문제인 것 같다.

주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 3-2 :


transform 을 이용해서, vector_c< int, 1, 2, 3 > 를 요소 ( 1, 4, 9 ) 를 가진 형식 순차열로 변환하라.




풀이 :


이것은 boost::mpl::multiplies 메타 함수를 사용해 해결한다.



주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 3-1 :


transform 을 이용해서, vector_c< int, 1, 2, 3 > 를 요소 ( 2, 3, 4 ) 를 가진 형식 순차열로 변환하라.




풀이 :


각 요소에 1 씩을 더하는 것이 이 문제의 핵심이다. 


3 장을 총정리하는 관점에서 boost 의 메타 함수를 사용하면 다음과 같이 쉽게 문제를 풀 수 있다.



하지만 이런 식으로 풀라고 문제를 낸건 아니지 않을까 하는 찝찝함이 남기는 한다. 


여기에서 주의할 것은 isSame 과 isSame2 를 구하기 위해서 boost::is_same 메타 함수를 사용해서는 안 된다는 것이다. vector_c 메타 함수가 불릴 때마다 새로운 type 이 생성되는 것이기 때문에 boost::is_same 을 사용하면 모조리 false 가 나온다. 요소가 같은지를 검사하기 위해서는 mpl::equal 메타 함수를 사용해야만 한다.


주의 : 공부하면서 정리한 것이므로 오류가 있을 수 있습니다.


실습 과제 3-0 :


1.4.1 절에 나온 binary 템플릿에, N 에 0 과 1 이외의 숫자가 들어 있을 때 binary< N >::value 가 컴파일 오류를 일으키도록 하는 오류 점검을 BOOST_STATIC_ASSERT 를 이용해서 추가하라.




풀이 :


일단 원래 구현을 살펴 보자.



다음과 같이 BOOST_STATIC_ASSERT 를 추가할 수 있다.



전체 코드는 다음과 같다.



너무 단순해서 맞는 건지 떨떠름하긴 하다.

주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


C++ Template Metaprogramming 책의 [ 3.2. 고차 메타함수들 ] 을 보면 다음과 같은 문장이 있다.


메타함수들을 "일급( first-class ) 메타자료" 로 만듦으로써, transform 으로 거의 무한하게 다양한 연산들( 앞의 예에서는 곱하기와 나누기 )을 수행할 수 있게 된다.


여기에서 first-class metadata 의 용어 설명이 제대로 나와 있지 않다. 그래서 googling 을 해 봤는데도 제대로 된 용어 설명을 찾을 수 없었다.


그런데 googling 을 하다가 보니 비슷한 용어 설명을 찾을 수 있었다.


What is 'first class object'?


first-class object 는 동적으로 생성, 파괴될 수 있거나 인자로 넘겨질 수 있는 것을 의미한다.


예를 들어, 모든 C++ 의 object 들도 first-class object 이다. 그것들은 생성자나 소멸자 메서드를 사용해서 생성되거나 파괴될 수 있으며, 함수의 인자로서 전달될 수 있다. 그러나 C++ 의 함수와 메서드는 first-class 가 아니다. 왜냐하면 그것들은 실시간에 생성될 수 없기 때문이다.


많은 함수적 언어( functional language )에서 함수는 first-class object 이다. 왜냐하면 그것들은 다른 함수의 인자로서 넘겨질 수 있으며 런타임에 동적으로 생성되기 때문이다.


C++ 에서 class 는 first-class 가 아니다 -- 일부 언어에서( 예를 들어 LISP 의 CLOS ) 그것들이 first-class 임에도 불구하고 말이다( 왜냐하면 그 언어들에서는 class 가 object 이다 ).


출처 : http://www.catalysoft.com/definitions/firstClassObject.html


위의 설명으로 보았을 때, meta function 이 meta function class 에 대한 직접적인 정의 없이 placeholder 기능을 통해서 meta function 의 parameter 로 취급될 수 있는 상황을 가리키는 것 같다. 즉 placeholder 는 meta function 을 meta function class 와 같은 first-class object 로 변환해 주는 역할을 하는 것이다.



원문 : http://www.drdobbs.com/cpp/extracting-function-parameter-and-return/240000586

주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 가독성을 높이기 위해 잘 알려진 용어 및 발음이 비슷한 용어는 한글 표기합니다.


C++ 에서 함수 매개 변수와 반환값 추출하기


C++ 의 메타프로그래밍( metaprogramming ) 기능과 generic 프로그래밍을 사용하면, 매개변수 형식에 대한 우아한 파서를 생성하는 것이 가능하다. 명령줄 해석기( command-line interpreter ), 파서( parser ), 구문 검사기( syntax checker )들이 그런 응용프로그램들의 일부이다.


메타프로그래밍은 점점 더 대중적이 되어 왔으며 이제는 현대 C++ 프로그래밍의 필수적인 부분이다. 형식 정보를 이용하면, generic 프로그래밍을 구현하고 컴파일 시점 최적화 및 검사를 수행하는 것이 가능해 진다. 이 기사에서 나는 템플릿 메타프로그래밍을 사용해 함수에서 형식 정보를 추출하고 이 기법을 사용하는 응용프로그램의 예를 보여 줄 것이다.


가능한 실용적인 응용프로그램들 중 하나를 설명하기 위해서, 나는 기본적인 명령어 해석기나 REPL 과 같은 기능을 수행하는 오픈 소스 응용프로그램을 생성했다. 새로운 명령은  함수 포인터를 제공함으로써 등록된다. 이 명령 클래스는 입력 문자열을 평가하고 입력 매개변수를 분석( parse )하고( 매개 변수의 개수와 형식을 검사하고 필요하면 변환함 ), 명령을 실행하고, 결과를 제공하기 위한 충분한 정보를 가지고 있다.


여기 새로운 명령을 등록하는 코드의 일부가 있다; 이 경우 새로운 함수를 등록한다 :



( 다른 예제들은 src\cmd_test.cpp 에 연결된 파일들로부터 찾아 볼 수 있다 ) 명령줄에 앞에서 언급한 코드를 넣은 후에, 당신이 다음 문자열을 실행하려고 시도하면 : add 5 4, 명령어 해석기는 자동으로 그것을 분석하고, 부가적인 작업을 수행하고, 결과를 출력한다. 유효하지 않은 매개변수들을 넣으면, 그것은 무엇이 잘 못 되었는지 알려 줄 것이다 :


input : "add not a number"

output : "add : bad arguments, usage RV : INT add INT INT;"


Figure1 내 test 응용프로그램의 스크린샷.

Figure 1 : 실시간에 인자에 대한 형 검사를 하는 것을 보여 주는 REPL 구현. 


구현.


이 구현에서 나는 Andrei Alexandrescu 에 의해 소개된 type list 의 개념을 사용할 것이다.


여기에 type list 를 구현하는 기본 클래스가 있다 :



Type list 를 생성하기 위해, 나는 다음과 같은 방식으로 이 클래스를 사용한다 :



NullType 은 특별한데 "종료자( terminator )" 형식이라 불린다. 이는 list 의 끝을 찾는데 도움을 주며 NullType{} 이라고 단순하게 정의될 수 있다.


이제 내가 쉽게 모든 함수 형식들( 반환 형식 및 입력 매개변수 )을 열거하는 type list 를 생성할 수 있다고 가정하면, 나는 자동 형식 검사 및 문자열로 받은 데이터들에 대한 변환을 수행할 수 있을 것이다. 그래서 만약 매개변수들이 ( REPL 에서 처럼 ) 문자열로 전달되면, 그것들은 자동으로 그것들을 검사하기 위한 어떤 도구를 필요로 한다. 이를 위한 가장 쉬운 방법은 무엇일까?


나는 다음과 같이 하는 것이 가장 쉬울 것이라고 생각한다 :



이제 이 클래스를 사용해서 다음과 같이 질의를 다룰 수 있다 : pcmd->run( "5 10" );


그 클래스 자체는 매개변수들을 검사하고 매개변수들이 올바를 때 호출을 수행할 것이다. 이 기사에서 내가 문자열을 분석하고 그것을 해당 형식으로 변환하는 연산에 대해서 다루고 있지 않지만, 당신은 내 샘플 프로그램에서 그것의 예를 찾아볼 수 있다. 거기에서는 응용프로그램을 위한 디버그 콘솔을 구현함으로써 지금 설명하고 있는 기술에 대한 응용프로그램을 설명한다.


이제 자동으로 type list 를 생성하기 위해서 createCmd 를 구현하는 방식에 대해 살펴 보자. 이 함수를 구현하기 위해서 나는 템플릿을 사용할 것이다. 다음은 함수의 선언이다 :



여기에서 name 은 함수에 대한 포인터 앞의 첫 번째 매개변수로 전달된다. 이 함수를 구현하는 것은 직관적이다 : 이는 그 작업을 수행할 실제 클래스에 대한 래퍼( wrapper )로서 사용된다.


다음은 단일 매개변수 버전을 위한 구현이다( 매개변수를 가지고 있지 않은 함수들을 위해서 void 일 수 있다 ). 나는 그냥 cmdCreator 클래스에 모든 매개변수를 전달할 뿐이다.



두 개 이상의 매개변수들을 위한 구현은 다음과 유사하다 :



이제 cmdCreator 클래스를 만들어 보자.


선언은 매우 단순해 보인다 : template struct cmdCreator;


다음은 입력 매개변수가 하나 이거나 없는( void ) 함수를 위한 구현이다 :



당신도 봐서 알겠지만, static 함수인 createCmd 는 우리 시스템의 심장이다. 그것은 template 의 매개변수들에 기반해 올바른 방식으로 type list 를 생성한다. 설명을 위한 목적으로 여기에 두 개의 매개변수를 가진 함수를 구현한다 :



CmdBase 에 대한 포인터를 반환하는 동안 TypeList 클래스를 생성한다는 것에 주목하라. 나중에 보여 주겠지만, 나는 CmdBase 로부터 TypeList 를 상속해서 다형성( polymorphism )의 이점을 취했다.


이제 올바른 TypeList 를 생성할 방법을 가지고 있다. 그런데 이것을 어떻게 적용해야 할까?


앞의 예제를 고려해 보자. 먼저 자동 형식 검사를 추가하기 위해서 TypeList 를 확장하라.


단순함을 위해 우리가 이미 분석된 입력 문자열을 가지고 있고, 매개변수들의 리스트를 생성했고, 그 매개변수들은 Variant 클래스로 래핑( wrapping )되어 있다고 가정하자. Variant 클래스는 유용한 개념이다. 부가적인 정보들을 알고자 한다면 Boost.Variant 나 다른 구현들을 참고해 볼 수 있다. 다음은 형식 검사를 수행하는 방법을 보여 준다 :



또한 나는 TypeList< R, NullType > 클래스를 위한 특수화를 정의해야만 한다. 왜냐하면 우리는 위의 함수에서 재귀가 끝나기를 원하기 때문이다.



나는 NumEI 라는 도우미 템플릿 클래스를 하나 더 소개했는데, 이는 type list 안에서 type index 를 위한 카운트를 유지한다. 이것은 선언은 다음과 같다 :



다음은 구현이다 :



봐서 알겠지만, 그것은 자신의 Tail 형식에 대한 반복( recursion )을 사용한다.



이제 run() 메서드를 구현하자. 나는 executor 라는 도우미 템플릿 클래스를 사용할 것인데, 그것은 cmdCreator 클래스를 위한 기술과 동일한 기술을 적용할 것이다. 그러나 먼저 나는 run() 메서드를 제공할 것이다. 할 일은 올바른 특수화를 제공하는 executor::run 메서드를 호출하는 것이다.



executor 클래스는 다음과 같이 선언된다 : template struct executor;


첫 번째 템플릿 매개변수 T 는 함수 포인터인데, 그것은 TypeList 클래스에 의해서 전달된다. 두 번째 템플릿 매개변수는 함수 형식들의 개수이다. 보면 알겠지만, 나는 myIndex 라는 컴파일 시점 상수를 사용하는데, 그것은 NumEl 도우미 클래스의 도움으로 계산됐다.


두 개의 매개변수를 위한 구현은 다음과 같다 :



여러 개의 매개변수들을 위한 구현은 매우 직관적이다.


보면 알겠지만, 나는 myfp 를 사용하는데, 그것은 함수에 대한 포인터이다. 그러나 나는 어떻게 이 형식을 얻는 것일까? cmdCreator 에서 나는 함수 포인터를 TypeList 의 두 번째 매개변수로서 전달한다. 우리의 type list 는 그것을 위한 어떠한 템플릿 매개변수도 가지고 있지 않은데, 어떻게 우리는 것을 생성할까? 이를 위해 나는 템플릿에 기반한 기교( trick )을 사용한다.


그것이 작동하는 방식을 이해하기 위해서 소스 코드를 좀 더 파 보자. TypeList 가 생성되는 지점으로 돌아가면( cmdCreator::createCmd ), 우리는 실제로 필요로 하는 모든 정보를 가지고 있다 : R, T1, T2 등. 우리는 단지 TypeList 를 R (*fptr )( T1, T2, ... ) 으로 변환하는 어떤 방법이 필요할 뿐이다.


내가 이를 변환하기 위해서 사용하는 도우미 클래스들은 TypeConv( TypeConv1, TypeConv2 등. 제공된 형식의 개수만큼 )이다.


종료( terminal ) 클래스들을 위한 가장 단순한 특수화로부터 시작할 것이다. 왜냐하면 그것이 이 아이디어를 가장 명확하게 보여 주기 때문이다. 이 클래스는 단지 하나의 typedef 만을 가지며, 그것이 다이다 :



하나의 매개변수를 가지는 함수들은 다음과 같이 보인다 :



이제 TypeConv 의 generic 버전을 실험해 보자 : 



이 코드는 기본적으로 typedef 를 재귀적인 방식으로 생성한다. TypeConv1 의 첫 번째 매개변수는 TypeList 의 Head 형식이며, 두 번째는 TypeConv 의 템플릿 매개변수 U 이다.그것은 TypeList 로서 재형식화( reformatted )되었다. 다음은 TypeConv1 클래스이다 :



이 코드는 ( 위의 것과 비슷해 보이는 ) TypeConv2 를 호출한다. 예를 들어 U 가 NullType 이었다면, 종료 버전의 특수화가 사용된다. 그렇지 않으면 일반적인 접근이 사용되며 U 가 NullType 이 될 때까지 재귀가 계속된다.


그러한 클래스들을 구현하기 위해, 나는 TypeList 클래스에다가 단지 두 줄의 코드만을 추가할 수 있다 :



그렇다!


나는 분명히 함수의 형식을 위한 다른 템플릿 매개변수들을 추가할 수 있지만, 우리가 이미 가지고 있는 정보들을 사용해 같은 결과를 획득하기 위해서 템플릿 메타프로그래밍을 사용하는 방법을 보여주고 싶었다. 이 "형식 변환"은 다른 목적으로 사용될 수도 있다.


같은 방식으로  나는 함수에 대한 사용 정보를 자동으로 출력해 주는 기능을 추가할 수 있다. 하나의 가능한 구현이 아래에 나와 있다 :



나는 여기에서 같은 접근법을 사용했다 : Tail 형식에 대한 재귀 호출. Type2String 은 단지 type 을 위한 문자열 이름을 반환하는 단순한 클래스일 뿐이다. 나는 그것을 여기에서 보여 주지는 않을 것이다. 왜냐하면 그것의 구현은 매우 단순하지만, 당신은 첨부한 코드에서 전부 볼 수 있기 때문이다.


요약을 위해 TypeList 클래스에 대한 전체 코드를 다음과 같이 제공한다 :



개선 사항.


반환값을 사용하면 어떻게 해야 할까? 그것은 당신에게 달려 있다. 나는 단지 그것을 디버그 콘솔 출력에다가 찍기만 한다. 그래서 사용자들은 실행된 명령의 결과를 볼 수 있을 것이다. If you try to use the output value, recall that it might be void.


Another thing you can add is a compile-time check for function parameters to be of a fundamental class, if you would like to constrain them.


결론.


함수를 위한 형식 추출을 제공함으로써, 이제 나는 다양한 요구를 위해 함수 형식 매개변수들을 점검하기( inspect ) 위한 유연한 메커니즘을 가지게 되었다.


이 기사에서 제공되는 코드는 ( 많은 게임들이 그런 것처럼 ) 디버그 콘솔을 생성하기 위해 적용되었다. 그리고 여기에서 코드를 찾아 볼 수 있다 : http://dconsole.googlecode.com




Sergii Biloshytski 는 임베디드 프로그래머로서 경력을 시작했지만, 길을 바꿔서 게임 프로그래머가 되었다. Ubisoft 에서 그는 Blazing Angles, From DUST, Assassins Creed 등과 같은 타이틀들과 관련한 작업을 해 왔다.

주의 : 공부하면서 정리한 내용이기 때문에 오류가 있을 수 있습니다.


우리의 이전 실습 과제에서는 array, function 등에 대해서 다루지 않았다. 이번 실습 과제에서는 그러한 기능을 보충해서 기본형에 대해서는 거의( ? ) 완벽한 type_descriptor 를 만들고, 그것에 대한 서술문을 출력하도록 하겠다.


일단 이전의 우리 구현에 따르면 array 나 function 과 같은 형식을 다루기 위해서는 remove_xxx meta function 이 존재해야 할 것이다. array 야 remove_bounds 와 같은 meta function 이 있지만, function 을 제거하기 위한 remove 류의 meta function 은 없다. 결국 우리는 function 과 관련한 복합 형식을 분석하기 위해서는 부분 템플릿 특수화에 의존해야 한다는 결론에 도달하게 된다.


그래서 이전의 구현을 다음과 같이 수정했다. Template 부분 특수화를 사용한 버전이다. 아래 구현을 잘 살펴 보면 크게 어렵지 않을 것이라 생각한다. 그리고 함수를 특수화는 귀찮아서 parameter list 가 두 개인 것까지만 구현했다.



위의 코드를 실행하면 다음과 같은 결과가 나온다.



특이한 점은 함수의 parameter list 에 array 표현이 넘어 가게 되면 그것이 pointer 로 취급된다는 것이다.


주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 2-4 :


과제 2-3 의 해답을, Boost 형식 특질 라이브러리를 사용하지 않고 작성하라. 그리고 두 해답들을 비교하라.




이건 RTTI 의 기능을 사용해 간단하게 구현할 수 있었다.




그러나 안타깝게도 참조 정보가 전부 날라가는 것을 알 수 있다. 왜 이 정보가 날라가는지 검색을 해 보니 다음과 같은 답변을 찾을 수 있었다.


이는 typeid 가 작동하도록 제안된 방식이다. 당신이 typeid 를 reference type 의 type-id 에 적용할 때, type_info object 가 referenced type 을 참조한다.

ISO/IEC 14882:2003, 5.2.8 / 4 [ expr.typeid ] :

When typeid is applied to a type-id, the result refers to a type_info object representing the type of the type-id. If the type of the type-id is a reference type, the result of the typeid expression refers to a type_info object representing the referenced type. If the type of the type-id is a class type or a reference to a class type, the class shall be completely-defined. Types shall not be defined in the type-id.

출처 : http://stackoverflow.com/questions/5151875/typeid-doesnt-return-correct-type


그리고 변수에 대한 const 도 날라갔다는 것을 알 수 있다. 왜 이 정보가 날라가는지 검색을 해 보니 다음과 같은 답변을 찾을 수 있었다.


그것들은 같은 type 이 아니지만, typeid 연산자는 const 와 volatile 을 벗겨 낸다.

From section 5.2.8 [ expr.typeid ]

The top-level cv-qualifiers of the glvalue expression or the type-id that is the operand of typeid are always ignored.

출처 : http://stackoverflow.com/questions/8888960/why-typeid-returns-that-int-and-const-int-are-same-types


주의 : 공부하면서 정리한 것이기 때문에 오류가 있을 수 있습니다.


실습 과제 2-3 :


형식 특질 수단들을 이용해서, 그 인스턴스를 스트림에 넣었을 때 템플릿 매개변수의 형식 이름을 출력하는 type_descriptor 클래스 템플릿을 작성하라( 실행시점 형식 정보( RTTI )로 같은 효과를 얻을 수는 없다. C++ 표준의 18.5.1 [ lib.type.info ] 에서의 문단 7 에 따르면, typeid( T ).name() 이 의미 있는 결과를 돌려 준다는 보장이 없기 때문이다 ).



type_descriptor 의 템플릿 매개변수가 char, short int, int, long int 로만 구성된 복합 형식이라고 가정해도 좋다.




삽질 : 


이 문제를 풀기 위해서는 앞의 실습 과제들에서 했듯이 복합 형식을 분리할 수 있어야 한다. 그런데 굳이 모든 템플릿 특수화를  할 필요는 없다고 생각할 수 있다. 왜냐하면 문제에서는 "형식 특질 수단들을 이용해서"라고 했기 때문에 boost 의 meta function 들을 사용하면 된다고 생각했다.


그러나 boost 에는 is_integral 과 is_float 은 있지만 아쉽게도 is_char, is_short 같은 것은 존재하지 않는다. 그러므로 특수화를 하지 않고서는 이 문제를 해결할 수 없다.


그리고 복합형식인 경우와 복합형식이 아닌 경우를 구분할 수 있어야 한다. 그렇다면 우리는 몇 가지 패턴을 생각해 볼 수 있다.


  • T
  • const T
  • T const
  • T*
  • const T*
  • T const*
  • T* const
  • T&
  • const T&
  • T const&
  • T& const


그런데 이 각각의 경우에 과제에서 제시한 4 가지 형식이 결합되면 경우의 수는 ( 4 X 11 = ) 44 이며, 그에 대응해 특수화를 해야 한다. 문제에는 나와 있지 않지만 참조까지 고려하면 장난이 아니다. 여기에서 const T 와 T const 가 같고, const T* 와 T const*, const T& 과 T const& 이 같기 때문에 이를 배제하면 경우의 수는 ( 4 X 9 = ) 36  이다. 그러나 아직까지도 포인터의 참조라든가 이런 조합도 있기 때문에 특수화로는 답이 안 나온다.


어쨌든 일단은 두 개가 template 에서도 같은 복합형식으로 인지되는지 확인부터 해 봤다.



일단 다행히도 같은 형식으로 취급되었다.


이제 어떻게 하면 특수화를 줄여 볼 수 있을까라는 고민으로 이어졌다. 한참을 고민하다가, 한글을 완성형으로 하면 개수가 많아지고 조합형으로 하면 개수가 줄어들 듯이 그런 방법을 사용하면 어떨까 하는 생각이 들었다. 그러나 어떻게 하면 조합할 수 있을지 상상이 안 가서 결국엔 포기하고 googling 을 하고 말았다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_2-3


link 를 보는 순간 문제를 잘 못 이해하고 있었다는 생각이 들었다. 문제에서는 다음과 같은 형태로 출력하라고 되어 있다.



다음과 같은 형태가 아닌 것이다. 



계속해서 meta function 의 type 이나 value 를 가져다 쓰는 예제를 보고 있다가 보니 완전히 착각한 것이다. 사람의 고정관념이라는 것은 참 무섭다는 생각이 든다. 뭐 구현을 보면 문제를 제대로 이해하고 있더라도 엄청난 노가다를 하던가 제대로 풀지 못했을 것 같기는 하다. 가끔 template metaprogramming 을 사용해 문제 해결을 잘 하는 사람들은 천재가 아닐까라는 자기비하적인 생각도 든다.


풀이 : 


어쨌든 함수 호출 순서대로 설명하면 가장 먼저 정의할 것은 "<<" 연산자에 대한 overloading 이다. 이 연산자는 별건 없고 type_descriptor::print() 함수를 호출해 준다.



그 다음에는 type_descriptor template class 를 만들어야 한다. 그 안의 print() 함수는 boost 의 meta function 들을 사용한다. 일단 형식을 출력하는 순서를 정해야 한다. 그런데 각각의 형식이나 한정자들은 마치 차원이 다른 것처럼 동작하고 있기 때문에 tree 를 타고 내려 가듯이 재귀함수처럼 구현한다. 주의할 것은 다음 단계로 넘어 가기 전에 한정자나 포인터, 그리고 참조를 제거하고 항상 제일 앞에 단일 형식이 존재하는 것처럼 취급한다는 것이다.



전체 코드는 다음과 같다.




위의 링크에 들어가 보면 지금 구현이 가진 한계에 대해서 지적하고 보충하는 댓글들이 있다. 한 번씩 읽어 보면 도움이 더 많이 될 것이라 생각한다. 


여기에서는 단순한 형태에 대해서만 다루고 있다. 이것을 배열과 함수에 대해서 확장하기 위해서는 "실습 과제 2-5 풀이"를 참조하라.

주의 : 공부하면서 정리한 내용이므로 오류가 있을 수 있습니다.


실습 과제 2-2 :


boost::polymorphic_downcast 함수 템플릿은 포인터를 다형적 객체로 하향 변환하기 위한 static_cast 의 점검 버전을 구현한다.



릴리스 모드에서는 assert 가 사라지며, polymorphic_downcast 는 보통의 static_cast 만큼이나 효율적이 될 수 있다. 형식 특질 수단들을 이용해서, 이 템플릿을 포인터와 참조 인수 모두를 허용하도록 구현하라.





풀이 : 


이 문제는 함수 template 의 특수화에 대한 부분에서 시작한다. 문제에서 포인터와 참조 인수를 모두 허용하도록 하라고 했기 때문에, 이 문제를 풀 때 처음에는 아무 생각없이 함수 template 을 부분 특수화할 것이다.


하지만 함수 template 은 부분 특수화를 할 수 없다. 그러므로 overloading 을 통해 pointer 버전과 reference 버전을 만들어야 한다. 나머지는 기존에 우리가 했듯이 template 부분 특수화를 사용해서 처리하면 된다.


여기에서 한 가지 특이한 점은 boost::remove_reference 와 boost::remove_pointer 를 호출한다는 것이다. Target 은 pointer 나 reference 인 복합형식인 반면에 Source 는 복합 형식이 아니기 때문에, 두 형식을 가지고 boost::is_base_and_derived 를 호출하면 반드시 false 를 반환하게 된다. 그러므로 pointer 나 reference 를 복합형식에서 벗겨 내야 하는 것이다.


아래 코드를 잘 살펴 보면 크게 어려운 부분은 없을 것이다.



다른 사람들의 풀이를 보려면 다음 link 를 참조하라.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_2-2


주의 : 공부하면서 정리하는 것이기 때문에 오류가 있을 수 있습니다.


실습과제 2-1 :


아래의 예처럼, 임의의 복합 형식 c 를 첫 번째 인수로 취하고 c 에 있는 모든 형식 x 를 형식 y 로 치환하는 삼항 메타함수 replace_type< c, x, y > 를 작성하라.



둘 이하의 인수들을 가진 함수 형식만 지원해도 된다.




풀이 : 


이 문제의 요점은 "특정 type 을 찾고, 그것을 다른 type 으로 바꾸기" 이다. 원하는 type 들이 template parameter 로 넘어 오게 된다. 이 문제를 푸는 것은 두 개의 단계로 나뉜다.

  1. 복합 형식 c 에서 x 형식을 찾아 내기.
  2. 형식 x 를 형식 y 로 바꾸기.


사실 복합 형식이 아닌 것을  변환하는 것은 그리 어렵지 않게 할 수 있다.



그렇지만 복합 형식은 어떻게 해야 할지 감이 잘 안 올 것이다. Template 특수화 중에서는 복합 형식에 대한 부분 특수화 표현이 있던 것을 기억할 것이다. 예를 들어 어떤 template 이 pointer 형에 대해서 특수화되기를 기대할 때 다음과 같은 식으로 표현했다.



마찬가지로 우리는 배열을 표현할 수 있다.



그런데 _tMain 의 위의 code 를 실행하면 두 번째 문장( 우리 실습 과제 중 하나인 문장이다 )에서 false 를 반환한다. 그 이유는 이 부분 특수화가 기본형을 가진 array 에 대해서만 동작하기 때문이다. 두 번째가 true 를 반환하게 하기 위해서는 다음과 같은  부분 특수화를 더 만들어 줘야 한다.



하지만 이 code 를 build 하면 주석에 나와 있는 것처럼 살포시 error 가 나 준다. 그 이유는 "type * [ N ]" 이라는 부분에서 compiler 가 '*' 를 pointer 형 선언이 아니라 곱하기 연산자로 인식해 버리기 때문이다. 그래서 "typedef 가 끝났는데 ';' 가 없다"고 주장하는 것이다.


이 문제를 해결하기 위해서는 typedef 의 첫 번째 type 을 pointer 형으로 만들어 주면 된다.



Build 해서 실행하면 둘 다 true 가 나오는 것을 알 수 있다.


이러한 code 를 보고 있다 보면, template 특수화라는 것이 정말 "개노가다" 작업임을 알 수 있다. 그 때문에 boost 같은 library 들은 macro 를 엄청나게 많이 사용하고 있는 것 같다. 실제 hpp 파일을 열어 보면 대부분이 macro 로 되어 있음을 알 수 있을 것이다.


마지막으로 함수와 관련한 meta function 을 살펴 보자. 여기에서 한 번 또 막히게 되는데, 이 부분만 이해하면 meta function 에 대해 더 많은 감이 오게 된다. 현재 예제의 함수는 두 개의 type 을 가진다. 하나는 return type 이고, 하나는 parameter type 이다. 이렇게 함수를 분해해 놓으면 좀 더 쉽게 형태를 이해할 수 있다.



함수를 위한 부분 특수화의 특이한 점이라고 하면 return_type 과 parameter_type 을 가지고 있다는 점이다. 책에서는 이를 blob pattern 이라고 회피해야 한다고 했는데, 이 경우에는 정말 어쩔 수가 없다. 물론 이것도 함수를 만들어 주는 meta function 을 만들면 어떻게 될 것 같기도 하다.


( * )는 이름없는 함수의 pointer 형식이다. 이를 typedef 하기 위해서 "typedef return_type ( *type )( param_type )" 과 같은 표현을 썼다. 잘 모르는 사람은 함수 포인터로 검색해 보기 바란다.


책에 있는 실습 과제를 푼 최종 code 는 다음과 같다. boost 같은 library 들을 보며 어떻게 하면 단순화할 수 있을 지 연구해 볼 필요가 있을 것 같다.



주의 : 저만의 풀이이기 때문에 오류가 있을 수 있습니다.

실습 과제 2-0 : 


T 가 참조 형식이면 T 를, 그렇지 않으면 T const& 를 돌려주는 단항 메타함수 add_const_ref<T> 를 작성하라. 그리고 그 메타함수를 시험하는 프로그램도 작성하라. 힌트 : 결과를 검사할 때 boost::is_same 을 사용할 수 있다.




풀이 :


처음에 문제를 이해하기는 했지만 어떻게 해야 할지 감도 안 잡혔다. add_const 는 쉽게 상상이 가능했다.



왜냐하면 meta function 은 다음과 같이 정의되기 때문이다.


메타함수는 공개적으로 접근할 수 있는 type 이라는 이름의 내포된 결과 형식을 가진,

  • 모든 매개변수들이 형식인 클래스 템플릿

또는

  • 클래스

이다.

- 출처 : C++ Template Metaprogramming, 정보문화사.


그런데 조건이 걸리면서 문제가 생겼다. 조건이 걸렸기 때문에 type 을 반환하기 위해서 뭔가 내부적으로 또 다른 내포된 class template 을 사용하기는 해야 할 것같다고 생각을 했다. 하지만 그게 어떤 형태인지 책을 좀 뒤져 봐도 상상이 잘 안 갔다.


일단 reference 인지 판단을 어떻게 해야 하는지부터가 감이 오지 않았다. 특히나 분기문을 어떻게 구현해야 할지 감이 잘 안 왔다. 그래서 다른 사람들은 어떻게 했는지 googling 을 해 봤다.


http://www.crystalclearsoftware.com/cgi-bin/boost_wiki/wiki.pl?CPPTM_Answers_-_Exercise_2-0


보는 순간 "아!"하고 감이 왔다. Compile 시점에 판단을 수행해야 하기 때문에 이는 template 부분 특수화를 통해 이루어지고 있었다. 부분 특수화에 대해서 잘 모른다면, 이전에 번역한 문서인 Template Specialization and Partial Template Specialization 을 참조하기 바란다.


그 중에서 type 이 아니라 실제 value 를 가지고 특수화하는 부분을 이용할 수 있다. 아래 code 를 보면 XXX_impl 라는 식으로 suffix 가 붙은 부분이 있는데, 잘은 모르겠지만 내부적으로 ( 분기문처럼 ) 복잡하게 처리하는 경우에는 그런 suffix 를 붙이는 것 같아서 필자도 붙여 봤다. 나중에 정확한 용례를 알게 되면 소개하도록 하겠다.



필자는 나름대로 구현을 해 본 건데, 위의 링크를 보면 다른 사람의 구현도 참조할 수 있다. 자세히 안 봐서 잘은 모르겠지만, 나름 논쟁이랑 자랑질도 있는 것 같았다. 관심 있는 사람은 직접 구현도 해 보고 비교도 해 보면 좋을 것 같다.


이 과제를 하다가 느낀 거지만 boost 의 meta function 들을 분석해 보는 것도 꽤 좋은 학습이 될 것 같다. 앞으로 하나씩 분석해서 올려 봐야겠다.


결과는 다음과 같다.




원문 : http://www.cprogramming.com/tutorial/template_specialization.html

주의 : 번역이 개판이므로 원문을 참조하세요.

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

정보 : 가장 아래에 있는 관련 기사들도 같이 보시면 좋을 것 같습니다.


Template Specialization and Partial Template Specialization


Template Specialization


Template 을 가지고 작업을 하는 많은 경우에, 당신은 모든 가능한 data type 을 위한 범용적인 버전의 template 을 작성할 것이며, 그것을 그대로 둘 것이다 -- 모든 vector 는 아마도 정확히 같은 방식으로 구현될 것이다. Template 특수화라는 아이디어는 기본 template 구현을 덮어 써( overriding ) 특정 type 을 다른 방식으로 다루는 것을 의미한다.


예를 들어 대부분의 vector 들이 주어진 type 에 대해 배열로서 구현될 것이지만, 당신은 약간의 메모리를 절약하기로 결심하고, bool 의 vector 들을 vector 내의 하나의 요소( entry )가 각 bit 와 연관되는 integer 의 vector 로서 구현할 수 있다. 그래서 당신은 다음과 같은 두 개의 개별적인 vector class 들을 가질 수 있다. 첫 번째 class 는 다음과 같다.



그러나 그것이 bool 일 때 당신은 실제로는 이렇게 하고 싶지 않을 것이다. 왜냐하면 대부분의 system 들이 1 bit 가 필요함에도 불구하고 boolean type 을 위해 16 bit 나 32 bit 를 사용하기 때문이다. 그러므로 우리는 integer 배열로 data 를 표현함으로써 boolean vector 의 모양을 약간 다르게 만들어야 한다. 그것의 bit 들은 우리가 수동으로 조작하게 된다. ( bit 를 직접적으로 조작하는 것과 관련해 더 알고 싶다면, bitwise operators and bit manipulation in C and C++ 을 참조하라. )


이렇게 하기 위해 우리가 template 과 유사한 것을 가지고 작업하지만 이번에는 template 의 매개변수( parameter ) 가 비어있을 것이라는 것을 지정할 필요가 있다 : 



그리고 그 class 의 이름 뒤에는 특화된 type 을 넣는다 : class className< type >. 이 경우에는 그 template 이 다음과 같다 :



만약 특화된 버전의 vector class 가 범용 vector class 보다 더 다양한 interface( public method 집합 )를 가지고 있다면, 그것은 완벽하게 합리적일 것이다 -- 비록 둘 다 vector template 이지만, 그것들은 어떠한 interface 나 code 도 공유하지 않는다.


이 경우에 특화를 하는 가장 중요한 이유는 더 공간을 효율적으로 이용하는 구현을 허용하는 것이었음을 짚고 넘어 갈 필요가 있다. 그러나 당신은 이것이 쓸모가 있는지에 대해 생각해볼 수 있다 -- 예를 들어, 당신이 어떤 type 에 의존하는 templated class 에 부가적인 method 를 추가하고 싶지만 다른 type 에 대해서는 그렇게 하고 싶지 않은 경우이다. 예를 들어, 당신이 상속을 더 좋아하기는 하지만, 당신은 각 요소의 non-integer 부분을 반환하는 method 를 가지는 double 의 vector 를 가지고 있을 수 있다. 그러한 부가적인 기능을 가지지 않는 double 의 vector 가 존재하지 않도록 막을 특별한 이유는 없다. 그러나 만약 당신이 그것이 쟁점이라고 느끼고 그것을 막고자 한다면, 당신은 template 특수화를 사용해서 그것을 수행할 수 있다.


어떤 template 을 특수화하기를 원하게 되는 다른 경우는 당신이 template 을 저장하고자 하는 class 의 collection 에 구현되지 않은 어떤 동작( behavior )에 의존하는 template type 을 가지고 있을 경우이다. 예를 들어 당신이 > 연산자가 정의되어 있을 것을 요구하는 templated sortedVector 를 가지고 있고, 누군가에 의해서 작성된 class 집합은 overload 된 연산자를 포함하고 있지는 않지만 비교를 위한 함수를 가지고 있다고 한다면, 당신은 template 을 특수화해서 그 class 들을 개별적으로 다룰 수 있다.


Template Partial Specialization


부분 template 특수화는 위에서 기술한 전체 특수화와 비슷한 동기를 가진다. 그러나 이번에는 하나의 특정 type 을 위해서 class 를 구현하는 것이 아니라, 여전히 다른 매개변수화( parameterization )를 허용하는 template 을 작성하는 데서 끝날 것이다. 즉 당신은 하나의 기능( feature )을 특수화하는 template 을 작성하지만, 여전히 그 class 의 user 는 template 의 일부로서 다른 기능들을 선택할 수 있도록 한다. 예제를 통해 이를 좀 더 확실하게 하자.


Vector 의 개념을 확장하는 아이디어로 돌아가 보자. 그러면 우리는 sortedVector 를 가지고 있다. 이것이 어떻게 보일 것인지 생각해 보자; 우리는 비교를 위한 방법을 필요로 할 것이다. 좋다; 만약 연산자가 정의되어 있다면 그냥 > 를 사용하고, 그렇지 않다면 특수화를 한다. 그런데 이제 우리가 정렬된 vector 안에 object 에 대한 pointer 를 가지고 있기를 원한다고 가정해 보자. 우리는 pointer 의 값으로 그것들을 정렬할 수 있을 것이다. 단지 표준 > 비교를 수행하기만 하면 된다( 우리는 오름차순으로 정렬되는 vector 를 가지게 될 것이다 ).



이제 위의 반복문에서 우리는 T type 의 요소들 사이에서 직접 비교를 수행하고 있다는 것을 알게 될 것이다. 대부분의 경우에는  괜찮다. 그러나 아마 pointer 주소가 아니라 실제 object type 상에서 정렬되는 것이 더 말이 될 것이다. 그렇게 하기 위해서 당신은 code 를 다음과 같은 line 을 가지도록 작성해야할 필요가 있다 :



물론 그것은 non-pointer type 에 대해서는 작동하지 않는다. 여기에서 우리가 하고 싶은 것은 그 type 이 pointer 인지 non-pointer 인지 여부에 의존하는 부분 특수화이다( 당신은 pointer 의 multiple level 을 가지고 싶을 수도 있다. 하지만 그냥 단순하게 하자 ).


모든 pointer type 을 다루는 부분적으로 특수화된 template 을 선언하기 위해서, 우리는 이 class 선언에 다음을 추가할 것이다 :



여기에서 주목해야 할 두 개의 syntax point 가 있다. 첫 번째는 우리 template 매개변수 리스트가 여전히 매개변수의 이름을 T 라고 짓고 있지만, 그 선언은 class 의 이름 뒤에 T* 로 붙는다; 이는 컴파일러에게 모든 type 의 pointer 에 대해 더 범용적인 template 대신 이 template 을 연결해 달라고 요청하는 것이다. 두 번째는 T 가 이제 pointer 로 접근하는 type 이라는 것이다; 그것 자체는 pointer 가 아니다. 예를 들어 당신이 sortedVector< int* > 를 선언할 때, T 는 int type 을 참조할 것이다! 이는 당신이 그것을 별표( * )가 붙은 type 이 있을 때 그것을 매칭해 주는 형식이라고 생각하면 이해가 쉽다. 이는 당신이 구현을 할 때 좀 더 주의를 해야 한다는 것을 의미한다 : vec_data 는 T** 이다. 왜냐하면 우리는 pointer 로 구성된 동적 크기의 배열을 필요로 하기 때문이다.


당신은 sortedVector type 이 실제로 이렇게 동작기를 원하는지 궁금해 할 수 있다 -- 결국 만약 그것들을 pointer 의 배열로 삽입한다면, 당신은 그것들이 pointer type 에 의해 정렬될 것을 기대할 것이다. 그러나 이렇게 하는 것에는 실질적인 이유가 존재한다 : 당신이 object 에 대한 배열을 위해 memory 를 할당할 때, 반드시 각 object 를 생성하기 위해 기본 생성자가 반드시 호출되어야만 한다. 만약 기본 생성자가 존재하지 않는다면( 예를 들어 모든 object 가 생성되기 위해서는 어떤 data 를 필요로 한다면 ), you're stuck needing a list of pointers to objects, 그러나 당신은 아마도 그것들이 실제 object 들 자신이 정렬되는 것처럼 정렬되기를 원할 것이다.


그건 그렇고 당신은 template 매개변수에 부분 특수화를 수행할 수도 있다 -- 예를 들어 만약 당신이 fixedVector type 을 가지고 있는데 그것이 ( 동적 memory 할당 비용을 피하기 위해서 )  class 의 사용자가 저장될 type 과 vector 의 길이를 모두 지정할 수 있도록 허용한다고 하면, 다음과 같은 코드를 볼 수 있게 될 것이다 :



그리고 나서 당신은 다음과 같은 구문으로 boolean 들을 부분적으로 특수화할 수 있을 것이다 :



T 가 더 이상 template 매개변수가 아니기 때문에 그것은 template 매개변수 리스트 밖에 있으며, length 만 남게 된다는 데 주의하라. 그리고 그 length 는 ( 범용 template 선언을 할 때는 이름 뒤에 아무것도 지정하지 않는 것과는 다르게 ) 이제 fixedVector 의 이름으로 나타난다. ( 그건 그렇고 template 매개변수가 non-type 이라는 것을 보고 놀라지는 마라 : 그것은 완벽하게 유효하며, unsigned 와 같은 integer type 인 template 인자들을 가지기 위해 유용하게 쓰이기도 한다. )


최종 구현 세부사항이 부분 특수화까지 왔다 : 컴파일러는 완벽하게 범용적인 type 들에 대한 조합, 어떤 부분 특수화, 그리고 완전한 특수화가 존재할 때 어떤 특수화를 선택할까? 일반적인 경험 법칙( rules of thumb )은 컴파일러가 가장 명확한( specific ) template 특슈화를 선택할 것이라고 이야기한다 -- 가장 명확한 template 특수화는 다른 template 선언들에 의해서 받아들여질 수 있는 template 인자들을 가지고 있지만, 같은 이름을 가진 다른 template 들이 받아들일 수 있는 인자를 모두 받아들일 수는 없는 template 을 의미한다.


예를 들어 만약 sortedVector< int* > 가 memory 위치에 의해 정렬되기를 원한다고 할 때, 당신은 sortedVector 에 대한 전체 특수화를 생성할 것이다. 그리고 sortedVector< int* > 를 선언하면 컴파일러는 pointer 에 대해 덜 명확한 부분 최적화를 선택할 것이다. int* 는 전체 특수화와만 일치하지만 double* 과 같은 다른 pointer type 와는 일치하지 않기 때문에 그것이 가장 근접하게 특수화된 것이다. 반면에 int* 는 명백히 다른 template 들이 받아들이는 매개변수가 될 수 있다.




Related articles.

C++ Templates Quiz : 당신의 template 지식을 테스트해 보세요.

Templated classes in C++ : templated class 에 대한 배경 정보.

Templated functions : 함수 template 에 대해 더 많이 배워 보세요.


'Programming > C++' 카테고리의 다른 글

[ 번역 ] Universal CRT 소개  (0) 2015.12.28
[ 번역 ] C++ Modifier Types  (0) 2013.01.02
[ 번역 ] Function Call Operator ()  (0) 2012.12.24
[ 번역 ] explicit( C++ )  (0) 2012.12.24
[ 번역 ] C++ Metadata - Part 1, Singletons and Lookup  (0) 2012.10.31

C++ Metadata - Part 1, Singletons and Lookup


원문 : http://seanmiddleditch.com/journal/2012/01/c-metadata-part-i-singletons-and-lookup/

주의 : 허락받고 번역한 것이 아니므로 언제든 내려갈 수 있습니다.

주의 : 번역이 개판이므로 이상하면 원문을 참조하십시오.


Class metadata 는 C++ application 이 C#, Python, Java 와 같은 다른 고수준 언어에서 이용할 수 있는 runtime reflection 과 introspection 의 꽤 많은 부분을 가질 수 있도록 해 준다. C++ 은 어떠한 실제 metadata system 도 가지고 있지는 않지만( dynamic_cast<> 을 다루는 데 급급한, 거의 사용되지 않는 typeinfo/RTTI system 은 배제하고 ), 그 작업을 수행하고 만족할 만한 성능을 내고 사용하기 쉬운 시스템을 빌드할 수 있다. 이것은 엄청나게 큰 주제이기 때문에 이 기사를 몇 개의 시리즈로 분할할 것이다. 오늘은 metadata system 을 위해 가장 유용한 singleton pattern 과 몇 개의 입문 주제 및 대안들에 대해서 이야기할 생각이다.


Why Metadata


여러 번 들어 왔던 질문은 "왜 아무도 이것과 비슷한 system 을 원하지 않는거죠?", "정말 느리거나, memory 를 많이 차지하거나, 덜 유연한 해결책들보다 계산 비용이 더 많이 들지 않나요?" 이다. 나는 보통 이러한 질문들을 오래된 "game 을 위해서 가상 함수는 너무 느리지 않나요?" 나 다소 오래된 "component 나 data-driven 설계는 유연성을 증대하기에는 비효율적이지 않나요?" 라는 질문과 동일하다고 생각한다.


Metadata 를 사용하는 이유는 단순히 다음과 같다 : game engine 에서 runtime data-driven system 을 위한 metadata 는 부가적인 유지 비용이나 버그를 최소화하는 것을 가능하게 한다. Metadata system 은 부가적인 코드를 거의 추가하지 않고 단ㅅ누한 factory 들을 생성하는 것을 가능하게 한다. 그것들은 game object 들을 위한 serialization 을 사소하게 만든다. 그것들은 game editor window 나 debug toolbar 에서 편집중인 property 들을 매우 단순해지도록 만들어 준다. Metadata 는 강화된 debugging output 을 지원한다. Metadata 는 개체를 문서화하는 것을 돕는다. Metadata 는 script binding 을 위해서도 사용될 수 있다.


This all stems from the basic notion of what metadata is in programming language. Metadata 는 단지 base type system 에 부가적인 정보를 추가한 것 뿐이다. 매우 단순한 예제로 class 의 이름을 들 수 있다. C++ 에서는 runtime 에 보통 다음과 같은 질문을 던질 방법이 없다, "내 Foo* 변수에 의해 참조되는 class instance 의 이름은 무엇인가요?"( 그렇다, C++ 의 RTTI 와 그것의 typeid, typeinfo 는 이름을 제공하지만, 그것은 보통 당신이 기대하거나 원하는 이름이 아니다. ). 만약 그 class 가 dynamic polymorphism 이 아니라면, 그 대답은 단지 "Foo" 일 것이다. 그러나 그것은 우리에게 단순하지 않다. 우리가 template 과 metaprogramming 을 사용한다고 해도 우리는 앞의 object 를 위한 선언을 살펴 보는 것만으로는 object 의 실제 type 을 알수 없을 것이다.


적절한 metadata system 을 가지고 있다면, 우리는 class를 ( 혹은 심지어 기본형까지 ) 표현하는 object 를 획득하게된다. 우리는 metaprogramming type traits 와 비슷하지만 더 많은 정보를 가지고 있고 그것이 실시간에 동작한다는 점은 다른 무엇인가를 가질 수 있다. 우리는 다음과 같이 코드를 작성할 수 있을 것이다.



Sample 에서 보여 주듯이 현재 내 project 에서 metadata 를 획득하는 것의 가장 큰 용도는 game object 의 data-driven runtime composition 인데, 이는 단순한 text file 을 사용해 매우 다양하고 복잡한 방식으로 game entity 들을 customize 할 수 있게 만들어 준다. 또한 우리는 그것을 configuration system 을 위해서 사용하기도 하고, debugging macro 로 사용하기도 하는데, 우리의 editor 는 그것을 proeprty 편집을 위해서 사용하기도 한다.


위의 성능 관련 질문들에 대해 간단하게 답하면, 내가 설명할 system 은 CPU time 과 memory 에 있어 매우 효율적이므로 별로 의미가 없는 질문이다. 심지어는 특정 API cleanliness 의 비용에서는 더 빠를 수도 있다. 나는 가장 쉬운 use approach 에 대해서 설명하면서, 가장 효율으로 변경될 수 있는 영역에 대해서도 언급할 것이다. 내 project 에서 나는 road approach 의 중심을 향해 갈 것이며, 그것은 여기에서 설명하는 것보다는 약간 다루기 어렵지만 더 좋은 효율성을 가진 API 를 가지고 있다. 


( 원문 : Touching briefly on the questions above about performance, it's worth noting that the system I'll be describing is highly efficient in terms of both CPU time and memory. It's possible to get even more efficiency at the cost of some API cleanliness. I'm going to describe the easier to use approach, but I'll note the areas where things could be changed to maximize efficiency. In my projects I lean towards a middle of the road approach, which has an API slightly more cumbersome than what I describe here, but which has a bit more efficiency. )


Metadata Class


우리가 필요로 하는 첫 번째 것은 metadata object 를 위한 실제 class 이다.( 그렇다, 이 글의 마지막까지 가면 우리 metadata system 은 자신을 설명할 수 있게 될 것이다. ) 특별히 복잡한 것이 필요하지는 않지만, 당장은 극단적으로 단순한 형태를 유지할 것이다. 우리 metadata system 은 object 의 type 과 size 를 출력하기에 충분한 정보만을 가지게 될 것이다. 다음 article 들에서는 그것의 기능을 확장할 것이다.



Metadata Registry


Metadata 의 핵심적인 기능 중 하나는 class 의 instance 를 먼저 얻지 않아도 이름으로 class 에 대한 metadata 를 찾아내는 능력이다. 예를 들어 이는 factory system 에 유용하다.


그 아이디어는 모든 Metadata object 를 관리하는 전역 system 이 존재해야만 한다는 것이다. 이를 동작하게 만드는 데는 여러 가지 방법이 존재하는데, 단순하지만 비효율적인 것부터 매우 복잡하지만 ( 실용적으로는 ) 비용이 들지 않는 것까지 다향하다. 나는 단순한 접근법에 대해서 다룰 것이다.


가장 기본적인 수준에서, Metadata object 들을 다루는 class 를 만들 필요가 있으며, 그 class 를 사용해 자신들을 등록하는 Metadata object 들을 가지고 있게 도니다. 그 class 는 초기화 순서 문제( order-of-initialization problem )을 피하기 위해서 static method 와 member 들만을 사용할 것이다( 그러므로 그것들은 원한다면 몇 개 안 되는 c-style 의 function 들과 global 들이 될 수 있다 ).



이를 위해 필요로 하는 것보다 더 많은 것들이 존재하지는 않는다. 그러나 이 system 을 위해서 std::string 이나 std::map 을 사용하는 것이 그리 효율적이지는 않다 . 특히 메모리 제약이 심한( memory-contstrained ) 환경을 위해서는 이들을 사용하는 것을 피하는 것이 좋을 수도 있다. 


std::string 의 사용을 피하는 것이 가장 쉽다. C-style 문자열 포인터를 사용하지 않고 Metadata 의 이름에 대한 복사본을 만들 이유가 없다. std::map 인스턴스를 위한 custom comparator 를 지정할 필요가 있지만, 그것은 가능하면 trivial exercise 여야 한다.


std::map 도 사용하지 않으려면 약간의 트릭이 필요하다. 우리는 Metadata object 에 대한 ( O(logN ) 보다는 나은 ) 효율적인 검색 비용을 유지해야만 한다. 그것은 우리가 그것들을 잘 동작하는 tree 에 넣기를 원할 것이라는 것을 의미한다. 내가 사용해 온 접근법은 실제로 내 tree 를 작성하는 것이며, 거기에서 Metadata object  들은 그 자체로 node 가 된다; that is, the Metadata class has the child pointers and such necessary to implement a binary tree ( or a red-black tree, or so on ). The consequence is a lot of extra code since std::map can't just be reused, but there are absolutely zero runtime memory allocations required for the metadata system, the memory cost of metadata is as good as or better than std::map, and the runtime complexity is the same


만약 compile-time string hashing 이 Visual C++ 에서 가능했다면, 우리의 "완벽한" hash 를 사용하는 고정 크기 hash table 을 사용할 수 있는 선택지가 있었을 것이다. 하지만 슬프게도 Visual Studio 11 SP1 까지는 ( Microsoft 가 결국 GCC 와 Clang 이 이미 가지고 있던 것과 같은 constexpr 지원을 하게될 때 까지는 ) 이 방식이 선택지가 될 수 없다.


만약 당신이 자신의 tree 를 구현하려고 한다면, 나는 template 을 섞은 형태를 추전한다. 왜냐하면 그것은 나중에 metadata 에 대한 같은 문제들을 가지고 있는 ( 그리고 당신이 단순한 방법을 선택한다면 Metadata 의 또 다른 std::map member 일 뿐인 ) property 를 붙일 때 유용할 것이기 때문이다.


등록에 대한 것을 마치기 전에, Metadata 의 생성자는 자신을 manager 에 등록시키기 위해 MetaManager::registerMeta( this )를 불러야만 한다.


Name-Based Singletons


우리는 Metadata class 를 가지고 있으므로 다음과 같은 질문을 할 수 있다. "어떻게 각 class 에 대한 Metadata object 의 instance 를 생성해야 하나요?". 이를 위해서는 여러 가지 방법을 사용할 수 있다. 나는 다른 engine 들에서 볼 수 있는 방법들을 거의 대부분 사용해 보았고, 그것들은 장단점을 가지고 있다. 단순한 기법으로 시작해서 내가 최고라고 느끼는 것을 만들어 보자.


첫 번째 방식은 각 class 에 대한 전역 변수를 만드는 것이다. 예를 들어 내가 GameObject 라는 class 를 가지고 있었다고 한다면, 나는 g_MetaGameObject 라는 전역 변수를 가지고 있을 것이고, 그것은 Metadata 의 instance 이다. 단순하다. 몇 개의 매크로가 이러한 전역 변수들에 대한 선언, 정의, 참조를 쉽게 만들어 준다.



끝내주게 간단한다. 마지막 매크로를 제외하면 말이다. 그것은 좀 문제가 있으며, 이 방식을 내가 추전하지 않는 큰 이유이다. 특정 object 를 위한 metadata 를 실제로 획득하기 위해서, 그 object 는 getMetadata() 와 같은 method 를 가지고 있어야만 할 것이라는 것이다. 이는 요구사항이자 의무사항이다. 공통 base class 가 존재하지 않으며, 단지 metadata 를 사용하는 모든 class 가 getMetadata() 라는 method 를 가지고 있다. 그리고 그것은 class 를 위한 Metadata 의 pointer 를 요구한다. 



이는 다른 매크로를 추가해 약간 단순화될 수 있다. 이 method 는 virtual 이 아니며 상속이 필요없다. 그것은 자식을 가지고 있는 class 들에 대해서는 virtual 을 필요가 있으며, 그래야 META 매크로가 base pointer 상에서 불릴 때 기대하는 동작을 할 수 있다. 나는 그것을 단순하게 유지할 것이며, 단지 dynamic class 들을 위한 매크로의 버전을 보여줄 뿐이다.



이 방식은 세 가지 큰 문제가 있다. getMetaData() 라는 method 를 요구한다는 것이다. 다른 방식들이 dynamically polymorphic object 들에 대해 그것을 요구하는 반면 ( 하나의 우회방법이 있지만 비용이 크다 ), 그것은 metadata 에 참가하기 위해 class 를 수정하거나 확장할 필요가 없다. 특히 그것은 third party library 나 기본형과 같은 수정될 수 없는 class 들에 metadata 를 추가하는 것이 불가능하다.


두 번째 큰 문제는 이 방식이 template 내에서 동작하지 않는다는 것이다. 왜냐하면 compile-time metadata lookup 은 ( 적절한 metadata 전역 변수의 이름을 생성하기 위해서 ) type 의 이름에 대한 정보를 필요로 하는데, template 은 그것을 가지고 있지 않기 때문이다.


세 번째 큰 문제는 namespace 를 가진 type 이나 template parameter 를 가진 type 들이 metadata 를 가질 수 없다는 것이다. 왜냐하면 이러한 object 들을 위한 이름을 생성할 방법이 없기 때문이다. MyNamespace::MyClass 와 같은 class 식별자를 위의 macro 중 하나에 넘기면 부적절한 code 를 생산하게 될 것이다. MyTemplate 과 같은 type 을 넘기려고 시도해도 같은 일이 발생한다.


Template-Based Singletons


내가 발견한 두 번째이자 마지막 방식은 metadata 를 정의하고 검색하기 위해 template 을 사용하는 것이다. 여기에서 이 아이디어는 templated type 이 static method 와 static member 를 가질 수 있다는 데서 출발한다. 다른 모든 static member 와 static local variable 들이 수행하는 것과 같은 규칙에 의해, a statically allocated in a template will exist once and once only in the problem( the One Definition Rule guarantees this ).


즉 metatdata 를 생성하기 위해 새롭고 단순한 templated type 과 매크로 집합을 생성하는 것이 가능하다는 것이다. 이 template 은 이전 방식에서의 몇 가지 단점들을 제거해 준다. 먼저 그것은 모든 type 을 위한 metadata 를 찾는 것을 허용하는데, type 의 이름이 아니라 type 에 대한 compiler 의 정보에 의존한다. 그래서 그것은 template 에서 작동한다. 두 번째로 각 class 를 위한 유효한 전역 식별자들을 생성할 필요가 없기 때문에, 그것은 namespace  안에 존재하거나 template parameter 를 가지고 있는 class 들을 쉽게 지원하고, class 를 확장할 필요 없이 사용될 수 있으며, 심지어는 int 나 float 과 같은 기본형과 함께 사용될 수 도 있다. 마지막으로 나중에 살펴 볼 template metaprogramming 의 일부 기능은 dynamic polymorphism 이 문제가 되지 않는 모든 경우에 대해 object 에 대한 getMetadata() method 를 필요하지 않게 만들어 준다.



마지막 매크로는 아마 약간의 설명이 필요할 것이다. 우리가 object 를 가지고 있다고 하자. decltype 이라 불리는 C++ 11 의 기능은 우리가 object 의 type 을 획득할 수 있도록 해 준다. 이는 쓸모가 없어 보이지만( 우리가 object 를 가지고 있다면, 우리는 그것의 type 을 어디엔가는 선언해야만 하고, 결국에는 그래서 우리는 그것이 무엇인지 이미 알고 있을 것이다 ), type 을 복사하기 보다는 매크로에 object 의 이름을 그냥 넘기는 것이 훨씬 더 편리하다. 특히 길고 보기 싫은 templated 이름들에 대해서는 더욱 더 그렇다.


META 매크로에 대한 이 구현은 한 가지 단점을 가진다; 그것은 dynamic polymorphism 을 사용할 경우 전혀 작동하지 않는다는 것이다. 만약 MyBase 에 대한 pointer 를 가지고 있지만 instance 는 실제로는 MyDerived 를 참조하고 있다면, META 에 의해 반화되는 Metadata instance 는 MyBase 의 instance 일 것이다. 좋지 않다. 우리는 이 기사의 나중 부분에서 이를 수정할 것이다.


당신은 아마 내가 MetaManager 에서 했던 것과는 다르게 MetaSingleton 에서의 singleton Metadata 를 구현했다는 것을 깨달았을 것이다. 그것은 두 가지 이유 때문이다. MetaManager::getMata() method 에서 찾을 수 있는 static local variable 은 보통  내부적으로 구현되는데, 이를 위해서 각 static local variable 을 위한 hidden global boolean 을 가지고 있으며 함수가 불릴 때마다 그 boolean 이 true 로 설정되었는지를 검사한다.bool 과 같은 global 혹은 primitive type 들은 항상 코드 실행 전에 0 으로 초기화되므로 hidden boolean 은 항상 getMetas() method 가 처음 불릴 때 false 인 것이 보장된다. 심지어는 global object 의 어떤 random constructor 에서 불린다고 해도 마찬가지이다. 그 첫 번째 호출에서 그 boolean 은 false 이므로 그 object 는 초기화되 후에 그 boolean 을 true 로 설정하기 때문에, static local object 가 단 한 번만 그리고 첫 번째 사용될 때만 초기화되는 것을 보장한다. 그러나 그 boolean 은 method 가 불릴 때마다 검사된다. metadata object 들은 일반적으로 game 의 성능에 민감한 부분에서 접근되지 않지만, 나는 불신자들을 위해 복잡한 metadata system 을 사용하고 있을 때 조차 raw bare-metal efficiency 를 입증하고자 했다.


Static member 를 사용하는 두 번째 이유는 그것이 그 member 가 명시적으로 cpp 파일에서 초기화되는 것을 허용한다는 것이다( 사실은 요구되는 것이다 ). 당장은 Metadata object 에서 class 이름을 설정하기 위한 초기화 동안에만 매크로를 필요로 한다. 내가 object 에 property 를 추가하는 방법과 다른 진보된 실시간 초기화를 수행하는 방법에 대해서 보여 주는 이 기사의 나중 부분에서 명시적인 초기화를 할 필요가 생길 것이다.


Robust Compile-Time Lookup via Partial Template Specialization


We've got a major problem in our template approach right not. 우리가 MetaSingleton<> 에 접근할 때마다 우리는 어떤 qualified type name 을 넣고 있다. Template 이 작동하는 방식 때문에, MetaSingleton 과 MetaSingleton 과 MetaSingleton 은 모두 다르다. 그리고 결국 자신만의 Metadata object 를 가지게 된다. 우리는 실제로는 그것을 원하지 않는다.


이 문제를 해결하기 위해서 두 가지 방법이 존재한다. 첫 번째는 std::remove_const<>, std::remove_reference<> 등과 같은 특별한 metaprogramming template 들을 사용하는 것이다. 그것들은 사용하기 쉽지만, 다음과 같은 경우에는 올바르게 사용하기 어렵다. 만약 우리가 "MyType const* const&" 과 같은 qualified type 을 가지고 있다면, 우리는 unqualified "MyType" 까지 내려가기 위해 그것을 std::remove* 용법들에  매우 깊게 넣어야만 할 것이다. 감사하게도 부분 template 특수화가 사용하기 더 쉬우며, 그것은 여기에서의 우리의 모든 문제들을 해결한다.


부분 template 특수화는 unqualified 버전만을 참조하는 MetaSingleton 에 대한 대안 버전을 생성할 수 있도록 해 준다. 최종 결과는 우리가 template parameter 에 넘기는 것이 무엇인지와 관계없다. 단지 unqualified 버전의 static Metadata instance 만이 계속 참조될 것이다. 이는 구현하기 어렵지 않다. 단지 타자를 좀 쳐야 할 뿐이다.



const 버전의 참조와 pointer 에 대한 특수화가 요구된다는 것에 주목하라.


독자를 위한 퀴즈가 있다; 완벽함을 위해, "volatile" 에 대한 특수화가 필요한가? "static" 과 "register" 에 대한 것은 어떻게 해야 하는가? ( Hint : C++ 에서 type qualifier 와 storage class 의 차이는 무엇이고, 그러한 어떤 keyword 들이 그러한 카테고리들로 나뉘었는가? 그리고 왜인가? )


Supporting Polymorphic Classes


우리 META 매크로는 여전히 dynamic polymorphism 을 지원하지 않는다. 우리는 우리가 관심을 가지고 있는 실제 object 의 type 을 위한 Metadata instance 를 획득할 수 있는 어떤 방법을 필요로 하며, 그것은 base type 에 대한 pointer 나 reference 를 사용해 우리가 그것을 접근하고 있는지 여부와 상관이 없어야 한다.


불행하게도 우리 object 상에 virtual getMetadata() method 를 필요로 하는 상황으로 돌아간다. 하지만 다행히도 template 방식을 사용하면, 이 method 는 dynamic polymorphism 이 무넺가 될 때만 필요하다. 또한 template 방식은 virtual method 를 생성하기 위해 template mixin 의 사용을 허용하며, 그것은 우리가 전에 사용했던 매크로보다 쉽고 이쁘다.



이는 단지 단순한 CRTP( Curiously Recursive Template Pattern ) 응용프로그램일 뿐이다.



이것이 더 이쁜지에 대해서는 논란이 있다. 나는 "real" base type 을 mixin template 의 parameter 로 만들어야 하는 것을 특별히 좋아 하지는 않는다. 하지만 대안이 없다. 반대로 매크로로 표현하면 다음과 같다 :



둘 다 잘 동작할 것이다( 그리고 동일한 레이아웃과 실행 코드를 생성해야만 한다 ). 그러므로 당신이 더 이해하기 좋고 보기 좋아 보이는 것을 사용하면 된다. 당신이 C++ 의 내장 RTTI 를 사용한다면, 그 template mixin 방식은 당신의 RTTI table 을 부풀리게 될 것이다. 그래서 그것은 당신이 매크로 방식을 사용하도록 만드는 결정 요인이 될 수도 있다.


Template Metaprogramming for Metadata Lookup


Template metaprogramming 은 우리에게 getMetaData() method 를 선택적으로 호출할 수 있도록 하는 기능을 제공해 준다. 그것은 보기 싫고 끔찍하고 정신착란을 일으키는 기교임에도 불구하고 아직도 사용중인 것이다. 이 시리즈의 다른 기사는 과도한 수동 개입 없이 Metadata object 의 기능을 확장하는 metaprogramming 을 사용할 것이다. 그래서 우리는 이 주제에 대해서 다뤄야만 할 것이다. Time to rip off the bandaid. 그냥 코드를 보자.



자. 매우 보기 싫다. 명확하지도 않다. There's an even uglier way to do this, and there's a lot of ways that one would think should work but won't ( with Visual Sutdio, at least ).


나는 여기에서 metaprogramming  의 핵심 기교에 대해 설명할 것이다. 만약 template metaprogramming 에 익숙하지 않다면, Modern C++ Design 이라는 책을 추천한다. The topic is big enough that I simply can't do it justice here, at least not without giving it an entire large article all to itself. 


MetaIsDynamic 으로 돌아 가 보자. 우리는 다음과 같이 정의된 boolean 결과를 가지고 있다 :



이상한 부분은 std::is_same, no_return, decltype, check( 0 ) 등을 사용하는 모든 mess 이다. 위에서 언급했듯이 우리는 내부적으로 type comparison 을 수행하고 있다. check( 0 ) 을 사용하는 것은 두 개의 서로 다른 return type 중의 하나를 가지게 될 함수 호출을 평가할 것이다; 만약 MetaType 이 getMetadata() method 를 가지고 있지 않다면, check<>() 함수의 return type 은 MetaIsDynamic 에 정의되어 있는 no_return 구조체일 것이다. 만약 MetaType 이 getMetaData() method 를 가지고 있다면, 반환값은 no_return 이 아닌 다른 무엇인가일 것이다. 나는 decltype 과 std::is_same template 을 사용해 return type 을 검사하고 있다. 전체 라인은 "만약 no_return 이 check( 0 ) 의 반환값과 같은 type 이라면 boolean 값은 false 이며, 그렇지 않으면 true 이다"  라고 읽혀질 수 있다.


그리고 나서 중요한 질문은 check<>() 의 return type 이 선택되는 방법이 무엇이냐이다. 이는 Substitution Failure Is Not An Error ( SFINAE ) 라는 template behavior 를 사용해 수행된다. 그러므로 우리는 모호하지 않은 두 개의 templated 버전의 함수가 존재하도록 정렬할 수 있다( 왜냐하면 그것은 에러, SFINAE 나 no 일 것이기 때문이다 ). 그리고 여기에서 그것은 우리가 지정한 ( templated type 이 지정된 method 를 가지고 있는지 여부와 같은 ) 어떤 조건에 대해 유효하지 않을 것이다.


그 기교를 사용하기 위해서, MetaIsDynamic 구조체는 두 가지 버전의 check<>() 함수를 가지고 있다.



첫 번째 버전의 check<>() 를 살펴 보자. 그것은 (  우리 "true" 값인 ) char 를 반환하며, and takes... some kind of mess. 매개변수 type 은 확실히 불쾌하다. 그러나 그것은 그것의 component 부분까지 쪼개 가면 그렇게 나쁘지는 않다. 매개변수는 끝 부분에의 별표( * )를 통해 확인할 수 있듯이 pointer 이다. 그것은 표현식의 decltype 에 대한 pointer 인데, 그것은 그것이 expression 이 평가하고 있는 모든 type 에 대한 pointer 임을 의미한다. 그 표현식은 어떤 pointer cast 상에서 getMetadata() 를 호출한 결과이며, decltype 은 getMetadata() 의 return type 이 무엇이든간에 평가하게 된다. Pointer cast 는 0 ( 다른 말로 NULL ) 으로부터터 template type U 로의 cast 이다. 그것은 단지 method 를 호출하는 "random" pointer 를 생성하는 쉬운 방법일 뿐이다. 왜냐하면 우리는 method 를 사용하는 표현식을 필요로 하며, 그것은 object 를 요구하기 때문이다. decltype 은 단지 expression 의 type 에서만 보이며 결코 실제로 expression 을 평가하지는 않기 때문에, 우리가 사용한 pointer 는 유효한 object 일 필요는 없다. 그래서 NULL 을 우리가 원하는 type 으로 casting 하는 것은 만족스럽다. 그래서 전체 매개변수는 "U::getMetadata() 의 return type 에 대한 pointer" 라고 읽혀질 수 있다.


어떤 사람은 "그렇지만 만약 type U 가 getMetadata() method 를 가지고 있지 않으면?" 이라고 질문한다. 자 그것은 compiler 가 check<>() 의 선언을 평가할 때 대답해야만 하는 정확한 질문이다. 그 매개변수 type 을 생성하기 위해서 그 표현식이 사용되었기 때문에, 매개변수 type 이 실제 유효한 type 이거나 substitution failure 가 존재하기 때문에, 이는 SFINAE 가 효과가 나타나는 곳이다. 만약 typename U ( 이는 항상 MetaType 과 같은 type 이다 ) 가 getMetadata() method 를 가지고 있지 않다면, check<>() 함수 선언은 그냥 버려지며 SFINAE 규칙에 의해 에러 없이 무시된다. 이것이 정확히 우리가 원하는 것이다.


두 번째 버전의 check<>() 는 단지 대비책일 뿐이다. 그것의 매개변수는 C++ 에서의 resolution precedence 때문에 ellipse 들이다. 그것은 pointer 인자를 받아들이지만, 첫 번째 버전의 check<>() 가 존재한다면 항상 그것의 의미가 명확해지도록 보장하기도 한다.


제출된 코드에서의 한 가지 실수는 그 type 상의 getMeatadata() 가 실제로 Metadata 에 대한 pointer 를 반환하는 것이 보장되지 않는다는 것이다. 그것을 검사하는 노력까지 할 수 있음에도 불구하고, 나는 그럴 필요가 없다고 주장하고 싶다; Metadata 를 획득하지 않는 "getMetadata" 라 불리는 method 들을 추가하는 것과 같은 바보같은 짓은 하지 말기 바란다. 문제는 없을 것이다.


이 새로운 MetaIsDynamic metaprogramming 구조체를 실제로 사용하는 것은 약간 더 많은 작업을 해야 한다. 새로운 template 특수화 집합들이 필요하며, 이제 다 된 것이다.



std::enable_if 를 사용함으로써 약간 더욱 metaprogramming 이 들어 갔다. 그것은 SFINAE 에 의존하는 작은 기교인데, which allows for a version of a function to exist based on some boolean constant. Boolean 상수는  우리 MetaIsDynamic 에 의해 생성된 것과 비슷하다. resolve<>() 함수 중 한 버전은 getMetadata() method 를 호출할 것이며, 다른 버전은 그냥 MetaSingleton 을 직접적으로 사용할 것이다.


이제 우리는 그것을 보기 좋고 사용하기 쉽게 만들기 위해 새로운 버전의 META 매크로를 필요로 한다.



decltype 은 실제로 그것의 인자를 평가하지 않는다는 것을 다시 기억하라. 그래서 'obj' 매크로 매개변수를 반복적으로 사용하는 것은 전처리기 매크로의 일반적인 이중 확장 문제에 위배되지 않는다.


It's worth nothing that when compiled with optimizations on, all of this metadata lookup code will compile away to either a trivial address lookup of a global object or ( for classes that have a virtual getMetadata() method ) a single virtual call to a function that simply returns the address of a global object. Stepping through the code in debug builds can be a little scary, though, as it may seem like there are hobajillion recursive calls ( especially when those lookup templates get hit ), but don't be fooled by how bumb a compiler is with all of its optimizations turned off.


Usage Examples


Metadata class 는 많은 것을 하지 않는다는 것에 유념하며, 여기에 system 이 사용될 수 있는 방법에 대한 몇 가지 예제를 수록한다. 내부적인 부분과 구현 세부사항들이 있었지만, 결국 가장 중요한 것은 그 작업들이 할 수 있는 것이 무엇인지 확인하는 것이 좋다. 



컴파일러와 표준 라이브러리 제작자에 의존해 다양한 크기 출력을 얻을 수 있음에 주의하라.


The Future


이 시리즈의 다음 기사에서 나는 Metadata class 를 더욱 유용하게 만드는 것에 대해서 살펴 볼 것이다. 특히 우리는 복제, 단순한 상속 검사( object 의 instance 를 요구하지 않는 dynamic_cast<> 를 생각해 보자 ), 정렬과 같은 부가적인 class attribute 들 혹은 is_abstract flag, 그리고 완벽한 getter/setter 에 의해 지원되는 named/typed class property 들을 포함하는 factory method 들을 추가할 것이다. 시간이 있다면 script language binding 을 생성하기 위한 method binding 에 대해서도 살펴 볼 것이다( 그건 큰 주제이다 ).


+ Recent posts