<MyRusakov.ru />

Разработчик игр на Unreal Engine

Разработчик игр на Unreal Engine

Этот комплект с нуля всего за 7 месяцев сделает Вас Unreal-разработчиком. И при этом учиться достаточно 1 час в день.

Начнёте Вы с основ программирования, постепенно перейдя к C++. Затем очень подробно изучите Unreal Engine, и после научитесь программировать на C++ в Unreal Engine. В конце создадите крупный проект на C++ в Unreal Engine для своего портфолио.

Комплект содержит:

- 416 видеоуроков

- 95 часов видео

- 1024 задания для закрепления материала из уроков

- 3 финальных тестирования

- 4 сертификата

- 12 Бонусных курсов

Подробнее
Подписка

Подпишитесь на мой канал на YouTube, где я регулярно публикую новые видео.

YouTube Подписаться

Подписавшись по E-mail, Вы будете получать уведомления о новых статьях.

Подписка Подписаться

Добавляйтесь ко мне в друзья ВКонтакте! Отзывы о сайте и обо мне оставляйте в моей группе.

Мой аккаунт Мой аккаунт Моя группа
Опрос

Какая тема Вас интересует больше?

constexpr в C++: простое практическое руководство (с consteval, constinit и if constexpr)

constexpr в C++: простое практическое руководство (с consteval, constinit и if constexpr)

Запрос: constexpr в C++ — что это и как правильно использовать? В этой статье вы разберёте, как переносить вычисления на этап компиляции, ускорять код и повышать его надёжность с помощью constexpr. Мы также рассмотрим if constexpr, consteval и constinit (C++20), покажем практические примеры и разберём частые ошибки.

Что такое constexpr и чем он отличается от const

Ключевое слово constexpr говорит компилятору: «это можно вычислить во время компиляции». В итоге переменные, функции и даже конструкторы могут участвовать в константных выражениях. Важно понимать отличие от const:

  • const — объект нельзя изменить после инициализации, но он не обязан быть известен на этапе компиляции.
  • constexpr — значение должно быть вычислимо во время компиляции (если все аргументы тоже константны).
#include <array>

const int runtime_n = 10;            // значение известно только в рантайме
constexpr int compiletime_n = 10;    // известно на этапе компиляции

std::array<int, compiletime_n> ok{}; // работает
// std::array<int, runtime_n> bad{}; // ошибка: размер должен быть константой времени компиляции

constexpr-переменные и проверка через static_assert

Часто удобно сразу проверять инварианты:

#include <cstdint>

constexpr std::uint32_t kMagic = 0xDEADBEEF;
static_assert(kMagic == 0xDEADBEEF, "Неверная константа!");

Если условие в static_assert ложно, сборка упадёт ещё до запуска программы — отличный способ ловить ошибки раннее.

constexpr-функции: компилятор решит, когда вычислять

Функция, помеченная constexpr, может выполняться как на этапе компиляции, так и в рантайме — в зависимости от аргументов.

constexpr int sqr(int x) {
    return x * x;
}

static_assert(sqr(3) == 9); // вычислится на этапе компиляции

int run(int n) {
    return sqr(n);          // вычислится в рантайме, если n не constexpr
}

Начиная с C++14, в constexpr-функциях разрешены локальные переменные, циклы и ветвления. Это делает их удобным инструментом для генерации таблиц, предрасчётов и валидации конфигурации.

Практика: предрасчёт таблицы квадратов

#include <array>
#include <cstddef>

constexpr std::array<int, 10> make_squares() {
    std::array<int, 10> a{};
    for (std::size_t i = 0; i < a.size(); ++i) {
        a[i] = static_cast<int>(i * i);
    }
    return a;
}

constexpr auto kSquares = make_squares();
static_assert(kSquares[9] == 81);

Массив kSquares полностью рассчитан на этапе компиляции — нулевые накладные расходы в рантайме.

if constexpr: выбор ветки на этапе компиляции (C++17)

if constexpr удаляет неактуальную ветку кода ещё на этапе компиляции. Это особенно полезно в шаблонах:

#include <type_traits>
#include <limits>
#include <stdexcept>

template <class T>
T safe_div(T a, T b) {
    if constexpr (std::is_integral_v<T>) {
        if (b == 0) throw std::invalid_argument("division by zero");
        return a / b; // целочисленное деление
    } else {
        return (b == T(0)) ? std::numeric_limits<T>::infinity() : a / b; // вещественное деление
    }
}

Ветвь, не подходящая по условию, даже не компилируется, что позволяет использовать код, который для другого типа мог бы быть некорректным.

consteval: вычислить обязательно при компиляции (C++20)

consteval делает функцию немедленно вычисляемой: её вызовы допустимы только в контексте константных выражений.

consteval int inc(int x) { return x + 1; }

constexpr int a = inc(5);  // ок
int foo(int v) {
    // int r = inc(v);     // ошибка: v не известно на этапе компиляции
    return v + 1;
}

Используйте consteval для критичных констант, которые должны существовать только как compile-time значения (например, вычисление размера таблицы, проверка протокола, масок битов).

constinit: инициализация на этапе компиляции, но переменная изменяема (C++20)

constinit гарантирует, что объект со статическим временем хранения будет инициализирован на этапе компиляции (или, по крайней мере, до старта программы), но при этом он не константный.

// Файл конфигурации
constinit int g_port = 8080; // гарантированно инициализирован до main(), но менять можно

int main() {
    g_port = 9090; // допустимо
}

constinit помогает избежать «order-of-initialization fiasco» между разными единицами трансляции и делает глобальные настройки предсказуемыми.

Пример: compile-time проверка параметров и конфигурации

#include <array>
#include <cstddef>

constexpr int kMaxUsers = 1024;
static_assert(kMaxUsers % 2 == 0, "kMaxUsers должен быть чётным для выравнивания");

constexpr int bucket_count(int users) {
    return users / 2; // простое правило шардирования
}

constexpr auto kBuckets = bucket_count(kMaxUsers);
static_assert(kBuckets == 512);

struct Shard {
    int id{};
};

constexpr std::array<Shard, kBuckets> make_shards() {
    std::array<Shard, kBuckets> s{};
    for (std::size_t i = 0; i < s.size(); ++i) s[i].id = static_cast<int>(i);
    return s;
}

constexpr auto kShards = make_shards();

Если кто-то изменит kMaxUsers на некорректное значение, сборка упадёт. Это дисциплинирует конфигурацию и снимает класс ошибок из продакшена.

Ограничения и подводные камни

  • Не всё можно в constexpr: ввод-вывод, системные вызовы, работа с внешними ресурсами — вне игры. Думайте о «чистых» функциях без побочных эффектов.
  • Аргументы решают: constexpr-функция выполнится на этапе компиляции только при константных аргументах. Иначе — обычный рантайм.
  • Не переусердствуйте: чрезмерные метапрограммы увеличивают время компиляции и сложность. Начинайте с простых и понятных оптимизаций.
  • if constexpr отбрасывает «лишнюю» ветку, но только если условие можно вычислить на этапе компиляции.
  • consteval строже, чем constexpr: вызов допустим только в константном контексте.
  • constinit применим к объектам со статическим временем хранения; значение должно быть корректно инициализировано до запуска программы.

Микро‑советы и лучшие практики

  • Используйте static_assert для проверки протоколов, размеров буферов и инвариантов на этапе компиляции.
  • Выносите «дорогие» предсказуемые вычисления в constexpr-функции, генерируйте std::array с готовыми значениями.
  • Для выборов по типам применяйте if constexpr и std::is_* типовые трейты.
  • Оставляйте понятные имена константам и документируйте смысл — compile-time магия должна быть читаема.
  • Собирайте с современным стандартом: -std=c++20 или выше, чтобы получить consteval/constinit и улучшения в constexpr.

Мини‑чеклист перед коммитом

  • Можно ли посчитать это во время компиляции? Если да — вынесите в constexpr.
  • Важно ли гарантировать compile-time вычисление? Если да — используйте consteval.
  • Глобальная настройка должна быть инициализирована до main()? Подумайте о constinit.
  • Покрыли ли ключевые инварианты static_assert-проверками?

Куда двигаться дальше

Освоив constexpr, вы начнёте писать более быстрый, предсказуемый и проверяемый на этапе компиляции код. Рекомендую прокачаться по современному C++ на практических задачах — это лучший способ закрепить материал. Посмотрите программу и первые уроки тут: Хочу прокачаться: курс «Программирование на C++ с Нуля до Гуру».

Итог: используйте constexpr для констант и функций, if constexpr для выбора ветки по типам, consteval когда нужно жёсткое compile-time, а constinit — для безопасной инициализации глобальных настроек. Это базовые кирпичики современного, аккуратного и быстрого C++‑кода.

Копирование материалов разрешается только с указанием автора (Михаил Русаков) и индексируемой прямой ссылкой на сайт (https://myrusakov.ru)!

Добавляйтесь ко мне в друзья ВКонтакте: https://vk.com/myrusakov.
Если Вы хотите дать оценку мне и моей работе, то напишите её в моей группе: https://vk.com/rusakovmy.

Если Вы не хотите пропустить новые материалы на сайте,
то Вы можете подписаться на обновления: Подписаться на обновления

Если у Вас остались какие-либо вопросы, либо у Вас есть желание высказаться по поводу этой статьи, то Вы можете оставить свой комментарий внизу страницы.

Порекомендуйте эту статью друзьям:

Если Вам понравился сайт, то разместите ссылку на него (у себя на сайте, на форуме, в контакте):

  1. Кнопка:

    Она выглядит вот так: Как создать свой сайт

  2. Текстовая ссылка:

    Она выглядит вот так: Как создать свой сайт

  3. BB-код ссылки для форумов (например, можете поставить её в подписи):

Комментарии (0):

Для добавления комментариев надо войти в систему.
Если Вы ещё не зарегистрированы на сайте, то сначала зарегистрируйтесь.