템플릿 메타프로그래밍 (Template Metaprogramming)¶
복잡한 템플릿 프로그래밍을 피하세요.
정의:
템플릿 메타프로그래밍은 C++의 템플릿 인스턴스화 메커니즘이 튜링 완전하며 타입 영역에서 임의의 컴파일 타임 계산을 수행하는 데 쓰일 수 있다는 사실을 이용하는 일군의 기법을 가리킵니다.
장점:
템플릿 메타프로그래밍은 타입 안전하면서 성능도 좋은, 대단히 유연한 인터페이스를 가능하게 합니다. GoogleTest, std::tuple, std::function, Boost.Spirit 같은 것들은 이것 없이는 불가능했을 것입니다.
단점:
템플릿 메타프로그래밍에 쓰이는 기법들은 언어 전문가가 아닌 사람에게는 이해하기 어려운 경우가 많습니다. 템플릿을 복잡한 방식으로 쓰는 코드는 읽기 어렵고 디버깅하거나 유지보수하기도 어려운 경우가 많습니다.
템플릿 메타프로그래밍은 컴파일 타임 오류 메시지를 극도로 나쁘게 만드는 일이 잦습니다. 인터페이스가 단순하더라도 사용자가 뭔가 잘못하는 순간 복잡한 구현 세부사항이 그대로 드러납니다.
템플릿 메타프로그래밍은 리팩터링 도구의 일을 어렵게 만들어 대규모 리팩터링을 방해합니다. 첫째, 템플릿 코드는 여러 맥락에서 전개되므로 그 변환이 모든 맥락에서 타당한지 확인하기 어렵습니다. 둘째, 어떤 리팩터링 도구는 템플릿이 전개된 뒤의 코드 구조만 나타내는 AST를 다룹니다. 거기서 다시 작성해야 할 원래 소스 구문으로 자동으로 거슬러 올라가기가 어려울 수 있습니다.
결정:
템플릿 메타프로그래밍은 그것 없이는 불가능했을 더 깔끔하고 쓰기 쉬운 인터페이스를 가능하게 하기도 하지만, 지나치게 영리하게 굴고 싶어지는 유혹이 되기도 합니다. 추가로 드는 유지보수 부담이 수많은 사용처에 걸쳐 분산되는, 소수의 저수준 컴포넌트에 쓰는 것이 가장 좋습니다.
템플릿 메타프로그래밍이나 그 밖의 복잡한 템플릿 기법을 쓰기 전에 다시 한번 생각하세요. 여러분이 다른 프로젝트로 옮긴 뒤에 팀의 평범한 구성원이 그 코드를 유지보수할 만큼 잘 이해할 수 있을지, C++ 프로그래머가 아닌 사람이나 코드베이스를 가볍게 훑어보는 사람이 오류 메시지를 이해하거나 자신이 호출하려는 함수의 흐름을 따라갈 수 있을지 생각해 보세요. 재귀적인 템플릿 인스턴스화나 타입 목록, 메타함수, 표현식 템플릿을 쓰고 있다면, 또는 함수 오버로드 해결을 탐지하려고 SFINAE나 sizeof 트릭에 의존하고 있다면, 너무 멀리 갔을 가능성이 높습니다.
템플릿 메타프로그래밍을 쓴다면 복잡성을 최소화하고 격리하는 데 상당한 노력을 들일 각오를 해야 합니다. 가능한 한 메타프로그래밍을 구현 세부사항으로 숨겨서 사용자가 마주하는 헤더는 읽을 수 있게 해야 하고, 까다로운 코드에는 특히 주석을 잘 달아야 합니다. 그 코드를 어떻게 쓰는지 꼼꼼히 문서화해야 하며, "생성되는" 코드가 어떤 모습인지도 어느 정도 설명해야 합니다. 사용자가 실수했을 때 컴파일러가 내놓는 오류 메시지에 각별히 신경 쓰세요. 오류 메시지는 여러분이 제공하는 사용자 인터페이스의 일부이며, 사용자 관점에서 그 메시지를 이해하고 대처할 수 있도록 필요하다면 코드를 다듬어야 합니다.
옮긴이 풀이¶
핵심: 복잡한 템플릿 프로그래밍을 피하라¶
템플릿 메타프로그래밍은 강력하지만(예: std::tuple, std::function, GoogleTest의 기반), 언어 전문가가 아니면 읽기·디버깅·유지보수가 어렵고 컴파일 오류 메시지가 끔찍해집니다.
"너무 멀리 갔다"는 신호¶
재귀 템플릿 인스턴스화, 타입 리스트, 메타함수, 표현식 템플릿, 오버로드 해결을 위한 SFINAE나 sizeof 트릭을 쓰고 있다면 대개 과한 것입니다.
꼭 써야 한다면¶
- 널리 재사용되는 소수의 저수준 컴포넌트에 한정하세요.
- 복잡성을 구현 세부사항으로 숨겨 사용자가 보는 헤더는 읽기 쉽게.
- 까다로운 코드에 주석을 충분히 달고, 사용법과 "생성되는 코드" 모습을 문서화하세요.
- 오류 메시지도 사용자 인터페이스의 일부 — 사용자가 이해하고 대응할 수 있게 다듬으세요.