C++0x 미리보기 7, 가변 인수 템플릿

류광, 2008/05/30 23:31
임의의 개수의 템플릿 인수들을 받는 템플릿을 좀 더 간단하게 구현할 수 있게 됩니다.

(이 글을 쓰는 현재 최신 State of C++ Evolution 문서는 2008/n2565입니다.)

가변 인수 템플릿은 말 그대로 템플릿 인수의 개수가 가변적인(물론 실행 시점이 아니라 컴파일 시점에서) 템플릿을 말합니다. 가변 인수 템플릿이 필요한 이유는 굳이 이야기하지 않겠습니다. TR1의 tuple이나 ‘형식에 안전한’ printf를 생각하면 금방 알 수 있을 것입니다.

현재의 C++ 표준에서 가변 인수 템플릿을 만들려면 상당히 지저분한 과정을 거쳐야 합니다. 이는 Boost(의 일부 라이브러리)의 소스 코드를 들여다보고는 질려버리는 이유 중 하나이기도 합니다. 현재의 표준에서 가변 인수 템플릿을 만드는(또는 흉내내는) 방법에 대해서는 Modern C++ Design이나 C++ Template Metaprogramming 같은 책에 자세히 나와 있으니 역시 생략하고요. 가변 인수 템플릿의 필요성, 중요성에 비해 그 구현이 너무 지저분하고 한계가 많으므로 언어 차원에서 가변 인수 템플릿을 지원하자는 것이 이 제안의 핵심입니다.

개수가 가변적인 템플릿 인수들을 언어 차원에서 지원한다고 할 때 혹시 컴파일 시점에서 형식들의 배열을 훑는 어떤 루프 구조 같은 것(이를테면 for_each_type<Args> ??)이 언어에 새로 추가되는 게 아닌가 생각할 수 있겠는데, 아쉽게도(?) 그런 것은 현재의 C++에서 너무 급격한 변화를 불러올 것입니다. 제안된 가변 인수 템플릿은 그냥 “루프는 재귀로”라는 통상적인 템플릿 메타프로그래밍의 원리를 따릅니다. 각설하고, 간단한 예를 보죠(N2080에서 발췌).

template<typename T, typename... Args>
struct count<T, Args...> {
    static const int value = 1 + count<Args...>::value;
}

template<> // 재귀의 끝
struct count<> { static const int value = 0;}

실행 시점 가변 인수 함수에서처럼 ...라는 표기가 쓰인다는 점을 알 수 있습니다. Args는 키워드가 아니고 그냥 사용자가 정한 식별자입니다. 이제

count<int, char, double>::value

라는 표현식이 주어진 경우, T == <int>, Args... == <char, double>이 됩니다. 따라서 value 우변의 count<Args...>는 count<char, double>이 되고요. 애초에 주어진 템플릿 인수들(<int, char, double>) 중 첫 번째 인수인 int가 “추출”되었음에 주목해야 합니다. 이러한 과정이 더 이상 인수가 없을 때까지 재귀적으로 반복됩니다. 즉, count<char, double>::value의 인스턴스화에서는 T == <char>, Args... == <double>이 되고, 그 다음 단계에서는 T == <double>, Args... == <>이 됩니다. 이제 count<Args...>::value는 count<>::value이므로 여기서 재귀가 끝납니다. 이처럼, 각 재귀에서 가변 인수 목록의 제일 첫 인수를 추출함으로써 모든 가변 인수를 차례로 훑는다는 것이 가변 인수 템플릿의 핵심입니다.

가변 인수 템플릿 “함수” 역시 비슷한 방식입니다. 다음은 가변 인수 템플릿을 이용해서 “형식에 안전한” printf 를 구현한 예입니다(역시 N2080에서 발췌). 필드 너비 지정자 같은 것은 지원하지 않는 단순화된 버전입니다.

void printf(const char* s) {
  while(*s) {
    if (*s == '%' && *++s != '%')
       throw std::runtime_error(
         "invalid format string: missing arguments");
    std::cout << *s++;
  }
}

template<typename T, typename... Args>
void printf(const char* s, T value, Args... args) {
  while (*s) {
    if (*s == '%' && *++s != '%') {
      std::cout << value;
     return printf(++s, args...);
    }
    std::cout << *s++;
  }
  throw std::runtime_error(
    "extra arguments provided to printf");
}

템플릿 버전에서 args라는 인수는 하나의 인수가 아니라 여러 가지 형식의, 여러 개의 인수들을 의미합니다. printf("%s%d%f!!", string("hello"), 10, 1.0f)의 경우 첫 재귀에서는 T value == const string& "hello"가 되고 Args... arg == int 10, float 1.0f가 됩니다. while 루프에서 "%s"에 도달하면 cout << value에 의해 "hello"가 출력되고, printf("%d%f!!!", 10, 1.0f)가 호출됩니다. 그러면 T value == int 10, Args... arg == float 1.0f이 됩니다. 같은 과정을 거치면 결국 printf("!!!")이 호출되며, 그러면 비템플릿 버전이 호출되어서 재귀가 끝납니다.

이 버전은 서식 지정자(%s,%d 등)와 해당 인수의 형식이 일치하는지(예를 들어 "%s"에 대한 value가 실제로 string인지)를 점검하지는 않습니다. 단지 %?들의 개수와 인수들의 개수가 일치하는지만 점검합니다. 사실 이러한 가변 인수 템플릿에서는 각 인수의 개별적인 서식 지정자들로 지정한다는 게 별로 무의미합니다. 그냥 해당 추가 인수가 ostream에 대한 << 연산을 지원하기만 하면 됩니다. 따라서 예를 들어 fs = "오늘은 %_월 %_일입니다" 같은 서식 문자열에 대해 printf(fs, 5, 31)이라고 호출해도 되고 printf(fs, "오", "삼십일")이라고 호출해도 되는 유연한 printf가 가능합니다. (printf의 구현에 대한 이야기는 단지 예일 뿐, 이 제안이 printf의 구체적인 구현 방식까지 제안하는 것은 아닙니다.)

N2080에는 이 외에도 여러 가지 재미있는 예들이 나와 있습니다. 기본적으로 이러한 가변 인수 템플릿은 최종 응용 프로그래머보다는 기반 라이브러리 작성자에게 큰 도움이 되는 기능이겠지만, 사실 모든 C++ 프로그래머는 잠재적으로 라이브러리 작성자의 본성을 가지고 있으므로(^^) 라이브러리 전문 개발자가 아닌 사람이라도 가변 인수 템플릿을 미리 공부해 두면 좋을 것입니다.

top
TAG ,
트랙백 0 : 의견 # + 8

Trackback Address :: http://occamsrazr.net/tt/trackback/196

comments powered by Disqus

(2013년 11월 10일자로 블로그에도 DISQUS 시스템을 도입했습니다. 기존 의견의 수정, 삭제, 댓글 추가는 여전히 가능합니다.)

  1. 곽용재 2008/06/23 11:02 PERMALINKMODIFY/DELETE REPLY

    들어가면 꽤 흥미롭다고 느껴진 것들이 하나씩 제안되어 좋긴 한데, 점점, 언어의 연합체에서 하나의 국가가 되어가고 있다는 느낌이 드네요. ^^




  2. 류광 2008/06/28 17:36 PERMALINKMODIFY/DELETE REPLY

    혹시 언어의 기능들 사이의 직교성에 대한 언급인가요? 흥미로운 주제인데 좀 더 논의해 보았으면 합니다...

    그나저나 이번 달 안에 한 꼭지 더 써야 그나마 월간 연재라고 우길 수 있을텐데 간단한 항목 하나 골라서 후딱 써야겠습니다...

  3. cppig1995 2008/07/30 10:50 PERMALINKMODIFY/DELETE REPLY

    AI Programming Wisdom 시리즈 잘 읽고 있습니다. :)
    그나저나 "std::runtime error" - 밑줄(underscore) 하나가 증발한 거 아닌지...

  4. 류광 2008/08/03 21:54 PERMALINKMODIFY/DELETE REPLY

    cppig1995님 고맙습니다~ 오타 맞습니다. 고칠게요~



  5. 김윤수 2008/08/18 10:18 PERMALINKMODIFY/DELETE REPLY

    좋은 글 잘 읽고 있습니다. 그런데... Concept 에 대한 설명은 안 해주시나요 ? ^^ 이번에 도입된 상당히 큰 변화가 아닐까 싶은데, 류광님 글을 통해서 더 자세히 알고 싶네요.




  6. 류광 2008/08/18 20:25 PERMALINKMODIFY/DELETE REPLY

    안녕하세요~ 김윤수님의 C++ 이야기 잘 읽고 있습니다. Concept는 글이 엄청 길어질 것 같아서 못 건드리고 있습니다 ㅠ.ㅠ rvalue ref나 lambda도 마찬가지이고요. 사실 지금까지 주로 간단하게 설명할 수 있는 것들만 이야기해왔는데 앞으로도 당분간은 그럴 것 같습니다. 다음 편 주제로는 위임 생성자를 생각하고 있습니다....



  7. NDos 2012/10/14 14:29 PERMALINKMODIFY/DELETE REPLY

    그런데 클래스의 생성자를 가변 인수 템플릿으로 구현하려면 어떻게 해야 되죠?
    생성자가 호출될 때마다 멤버들이 초기화되버리니까 재귀 호출을 할 수 없는 것 아닌가요?


  8. 류광 2012/10/16 22:40 PERMALINKMODIFY/DELETE REPLY

    일단 드는 생각은.... 가변 인수 템플릿의 인수들은 형식들이고, 멤버 초기화는 형식들이 모두 결정된 뒤에 일어날테니 괜찮지 않을까요?