Template metaprogramming
Тема дорожной карты · Modern C++ Developer
Template metaprogramming (TMP) — техника использования C++ шаблонов как языка программирования времени компиляции. Вместо рантайм-вычислений TMP позволяет генерировать код, выполнять вычисления, выбирать типы — всё компилятором, до того как программа запустится. Изначально (C++98) была побочным эффектом дизайна шаблонов; C++11/14/17 сделали её более вменяемой через constexpr, variadic templates, fold expressions; C++20 — через concepts и consteval.
Как это работает
Базовый приём — рекурсивные инстанциации шаблонов: template<int N> struct Factorial { static constexpr int value = N * Factorial<N-1>::value; }; плюс специализация Factorial<0>::value = 1. Компилятор инстанциирует цепочку и подставляет финальное значение. SFINAE (Substitution Failure Is Not An Error) — выбор шаблона по свойствам типа: std::enable_if_t<std::is_integral_v<T>> фильтрует non-integer типы. C++17 if constexpr — compile-time ветвление в одном теле шаблона. C++20 concepts — типобезопасная замена SFINAE: template<std::integral T> void func(T x). Variadic templates + fold expressions: template<typename... Args> auto sum(Args... args) { return (args + ...); }.
Когда применять
TMP уместен для библиотек обобщённого кода: контейнеры с compile-time-параметрами (std::array<T, N>), типизированные wrapper-ы (std::tuple, std::variant, std::optional), serialization (msgpack, Boost.Serialization), expression templates (Eigen, Blaze для линейной алгебры). В прикладном коде применяйте умеренно — TMP сильно усложняет понимание и сообщения об ошибках компилятора превращаются в роман на 1000 строк. C++20 concepts резко упрощают читаемость TMP.
Типичные ошибки
Глубокая рекурсия шаблонов — компиляция замедляется до часов на больших проектах, особенно с -O3 и debug-info. SFINAE без std::enable_if_t (с typename = void) — ломается при ABI-несовместимости между компиляторами. Перебор с TMP в публичных API — пользователи библиотеки получают «error: no matching function» вместо понятной диагностики (используйте static_assert или concepts с явным сообщением). Игнорирование if constexpr в C++17+ коде и продолжение SFINAE — лишнее усложнение и худшие сообщения об ошибках компилятора.
Современный подход TMP в C++20: вместо рекурсивных шаблонов с value пишут consteval функции, которые компилятор гарантированно выполняет во время компиляции. Concepts полностью заменяют SFINAE: requires(T t) { t.size(); } декларативно описывает интерфейс типа, ошибки компилятора становятся читаемыми. Variable templates (template<typename T> constexpr bool is_pointer_v = std::is_pointer<T>::value) делают type-traits лаконичнее. CTAD (class template argument deduction, C++17) убирает необходимость прописывать template-параметры в местах создания объектов.