Перейти к содержанию

Программирование во время компиляции с constexpr

Начало урока

Программирование во время компиляции с constexpr

Перенос вычислений из времени выполнения в компилятор - современный преемник метапрограммирования на шаблонах

Главная идея: Семейство constexpr сообщает компилятору, что функция или переменная пригодна для вычисления во время компиляции. Когда аргументы сами являются константами времени компиляции, результат вычисляет компилятор и запекает его в бинарный файл как литерал - процессору во время выполнения не остаётся никакой работы. Там, где метапрограммирование на шаблонах (TMP) кодировало вычисления в виде рекурсивных инстанцирований шаблонов, похожих на головоломки, constexpr позволяет писать обычные функции - с циклами, локальными переменными и if, - которые просто вдобавок выполняются во время компиляции.
К концу темы вы сможете:
  • отличать функцию constexpr (может выполниться при компиляции) от функции consteval (обязана);
  • понимать, когда переменная constexpr принудительно вычисляется при компиляции и чем это отличается от обычного const;
  • использовать if constexpr, чтобы отбрасывать ветви при компиляции и отказаться от значительной части кода на SFINAE и тег-диспетчеризации;
  • объяснять, почему constexpr в основном вытеснил TMP для вычисления значений, и называть случаи, где шаблоны и TMP по-прежнему нужны.

Тема опирается на Метапрограммирование на шаблонах, SFINAE и ограничения и Концепты (C++20) из этого курса. Предполагается, что вы уже читаете рекурсивный шаблон и трейт типа.

1. Функции constexpr - разрешение, а не обещание

Функция constexpr - это обычная функция с одним дополнительным свойством: ей разрешено выполняться во время компиляции. Произойдёт ли это на самом деле, целиком зависит от места вызова. Вызванная в контексте константного вычисления с константными аргументами, она вычисляется компилятором, и ответ сохраняется как литерал. Вызванная с обычным значением времени выполнения, она ведёт себя как любая другая функция и выполняется на процессоре. Одно определение - два возможных времени выполнения.

constexpr long factorial(int n) {
    long result = 1;                 // C++14: локальные переменные, циклы
    for (int i = 2; i <= n; ++i)     // и мутация - всё разрешено
        result *= i;
    return result;
}

constexpr long f5 = factorial(5);    // вычислит компилятор -> 120
int r = read_int();                  // некое значение времени выполнения
long fr = factorial(r);              // та же функция, выполняется во время работы

Обратите внимание, насколько это читаемее рекурсивного шаблонного факториала из темы Метапрограммирование на шаблонах. Это обычный императивный код. С C++14 функция constexpr может содержать циклы, несколько операторов, локальные переменные и их изменение; ограничение C++11 на единственный return давно снято.

Что считается контекстом константного вычисления? Места, где язык требует константное выражение:

  • инициализатор переменной constexpr или условие static_assert;
  • нетиповой аргумент шаблона, например std::array<int, factorial(5)>;
  • размер массива, значение перечислителя, метка case.
Функция constexpr, которая ни при каких аргументах не может дать константу, некорректна (диагностика не обязательна). Но корректная функция constexpr, вызванная с аргументами времени выполнения, - это совершенно нормально: она просто выполняется во время работы.

Продолжите этот урок на YourAIZone

В полном уроке — объяснение целиком, разобранные примеры, частые ошибки, тесты с мгновенной проверкой и ИИ-репетитор. Начните сразу как гость или создайте аккаунт, чтобы сохранять прогресс.

В полном уроке

  • Объяснение целиком
  • Разобранные примеры
  • Частые ошибки и как их избежать
  • Тестовые вопросы: 15
  • ИИ-репетитор, который отвечает на вопросы по теме
  • Отслеживание прогресса для ученика, родителей и учителя