Метапрограммирование шаблонов
Превращаем компилятор в программу, которая вычисляет типы и значения ещё до запуска вашего кода
- объяснить двухфазную модель: что вычисляет компилятор, а что — процессор;
- переносить вычисления на этап компиляции с помощью функций
constexprиconsteval(C++20); - исследовать и преобразовывать типы с помощью библиотеки
<type_traits>; - ветвиться по типам внутри одной функции через
if constexprвместо запутанных перегрузок; - обеспечивать контракты на этапе компиляции через
static_assertи понимать реальные компромиссы TMP.
1. Два мира: время компиляции и время выполнения
Каждая программа на C++ живёт в двух мирах. В мире времени компиляции компилятор манипулирует типами и константными выражениями, инстанцирует шаблоны и вычисляет код constexpr. В мире времени выполнения процессор исполняет машинный код, переживший компиляцию. Метапрограммирование — это искусство сделать как можно больше полезной работы в первом мире, чтобы второму осталось меньше.
Шаблоны вы уже знаете из промежуточного курса: template<typename T> позволяет одному определению обслуживать много типов. Ключевая мысль TMP в том, что инстанцирование шаблона само по себе является вычислением. Когда вы пишете std::vector<int>, компилятор запускает шаблон вектора с аргументом int и создаёт совершенно новый тип. Дайте шаблону другие аргументы — и он вычислит другие типы: это чистая функция из типов (и значений) в типы (и значения), исполняемая компилятором.
Зачем это нужно? Три конкретные выгоды:
- Скорость: значение, вычисленное на этапе компиляции, становится литералом в бинарнике; процессор не пересчитывает его.
- Безопасность: ошибка, пойманная при компиляции, никогда не дойдёт до пользователя.
static_assertпревращает логическую ошибку в ошибку сборки. - Обобщённость без затрат: один обобщённый алгоритм специализируется под каждый тип с нулевым ветвлением во время выполнения.