Как проверить, что код выполняется во время компиляции (compile-time)?

Ответ

В современном C++ для гарантии вычислений во время компиляции используются constexpr (C++11/14), consteval (C++20) и static_assert для проверки.

1. Использование constexpr и static_assert: Если constexpr-функция не может быть вычислена на этапе компиляции, код не скомпилируется.

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) { // Циклы разрешены в constexpr с C++14
        result *= i;
    }
    return result;
}

// Проверка во время компиляции
static_assert(factorial(5) == 120, "Compile-time computation failed");
static_assert(factorial(0) == 1);

int main() {
    constexpr int compile_time_value = factorial(6); // Вычисляется при компиляции
    int runtime_value = factorial(10); // Может быть вычислено и во время выполнения
    return 0;
}

2. Использование consteval (C++20): Функции, помеченные как consteval, должны давать результат на этапе компиляции. Вызов такой функции с аргументами, известными только во время выполнения, приведёт к ошибке компиляции.

consteval int square(int x) {
    return x * x;
}

static_assert(square(9) == 81); // OK

int main() {
    constexpr int a = square(5); // OK
    int runtime_input = 7;
    // int b = square(runtime_input); // Ошибка компиляции: аргумент не является константным выражением
    return 0;
}

3. Проверка типов и условий на этапе компиляции с if constexpr:

template<typename T>
constexpr auto getTypeSize() {
    if constexpr (std::is_integral_v<T>) {
        return sizeof(T);
    } else {
        // Этот branch отбрасывается на этапе компиляции для целочисленных типов
        return 0;
    }
}

static_assert(getTypeSize<int>() == 4); // Проверка для 32-битной платформы

Практическое применение:

  • Вычисления констант: Размеры массивов, математические константы.
  • Метапрограммирование: Генерация типов, проверка условий.
  • Оптимизация: Вынос вычислений из времени выполнения, если аргументы известны при компиляции.

Если static_assert срабатывает, вы получаете ошибку компиляции — это и есть доказательство, что проверка (или вычисление) произошла на этапе компиляции.

Ответ 18+ 🔞

А, ну вот, опять эти ваши константные вычисления, ебать мои старые костыли! Слушай сюда, я тебе сейчас на пальцах объясню, как заставить компилятор пахать за тебя, пока ты кофе пьёшь.

Раньше, ёпта, всё было просто: написал функцию — она в рантайме и работает. А теперь, блядь, можно заставить её вычислить всё заранее, прямо когда код собирается. Это как если бы ты, прежде чем поехать в магазин, уже точно знал, сколько сдачи тебе дадут. Удобно, да?

Вот смотри, самый распространённый способ — это constexpr. Объявляешь функцию с этим словом, и компилятор смотрит: "Ага, чувак хочет, чтобы я это посчитал сам". Если можешь — посчитает, подставит готовый результат. Если не можешь (например, переменная только в рантайме известна) — ну, тогда посчитается уже при запуске, не страшно.

constexpr int factorial(int n) {
    int result = 1;
    for (int i = 2; i <= n; ++i) { // Да, циклы тут уже можно, с C++14
        result *= i;
    }
    return result;
}

// А вот это — наш сторожевой пёс, ебать его в сраку. Если что не так — сразу вой!
static_assert(factorial(5) == 120, "Compile-time computation failed");
static_assert(factorial(0) == 1);

int main() {
    constexpr int compile_time_value = factorial(6); // Всё, тут компилятор уже посчитал 720 и подставил.
    int runtime_value = factorial(10); // А тут, может, посчитает сейчас, а может — когда программа побежит.
    return 0;
}

Но это, понимаешь, ещё цветочки. В C++20 пришла штука построже — consteval. Это вообще, блядь, террорист. Функция с такой пометкой обязана быть вычислена при компиляции. Никаких "а может, потом"! Попробуй сунуть ей переменную из рантайма — получишь ошибку компиляции, и будешь сидеть, чесать репу.

consteval int square(int x) {
    return x * x;
}

static_assert(square(9) == 81); // Ну тут всё ок, 9 — константа.

int main() {
    constexpr int a = square(5); // И тут ок.
    int runtime_input = 7;
    // int b = square(runtime_input); // А вот тут — ПИЗДЕЦ! Не скомпилируется! Аргумент не константа!
    return 0;
}

Ну и вишенка на торте — if constexpr. Это вообще магия, ядрёна вошь! Ты можешь в коде написать условие, и компилятор ещё на этапе сборки отрежет всё ненужное. Как будто ты говоришь: "Э, дружок-пирожок, если у нас тип целочисленный — оставь вот этот кусок кода, а остальное выкинь в помойку".

template<typename T>
constexpr auto getTypeSize() {
    if constexpr (std::is_integral_v<T>) {
        return sizeof(T);
    } else {
        // Для не-целых типов этот кусок кода даже не будет существовать в итоговой программе!
        return 0;
    }
}

static_assert(getTypeSize<int>() == 4); // Проверим на лету.

А где это всё применять-то, спросишь? Да везде, чувак! Размеры массивов задавать, какие-то сложные коэффициенты заранее просчитывать, проверять, что твой шаблонный костыль натянется на нужный тип. Оптимизация, блядь, чистая. Вместо того чтобы процессор в рантайме парился, всё уже готово.

И главный козырь — static_assert. Если он сработал и компиляция упала — поздравляю, ты только что доказал, что проверка случилась именно при компиляции. Живой, блядь, труп на месте преступления. Вот так-то.