Ответ
В современном 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. Если он сработал и компиляция упала — поздравляю, ты только что доказал, что проверка случилась именно при компиляции. Живой, блядь, труп на месте преступления. Вот так-то.