constexpr, consteval și programare în timp de compilare
Mutați calculul din timpul de execuție în timpul de compilare folosind constexpr, consteval și constinit. Creați tabele de căutare, validați configurațiile și utilizați containere constexpr.
Programare în timp de compilare în C++
C++ vă permite în mod unic să rulați cod real la timp de compilare. Aceasta înseamnă că compilatorul poate calcula valori, valida intrările, poate construi tabele de căutare și chiar poate rula algoritmi complecși - totul înainte ca programul să fie livrat. Rezultatul este încorporat direct în binar ca constante.
Evoluția programării în timp de compilare în C++:
- C++11:constexprintrodus — funcții simple, cu o singură returnare
- C++14:constexprrelaxat — bucle, variabile locale, instrucțiuni multiple
- C++17:if constexpr— ramificare în timp de compilare care elimină căile neutilizate
- C++20:consteval(trebuie să fie în timp de compilare),constinit(asigură că inițialitatea statică este în timp de compilare), containereconstexpr(vector,string)
- C++23:if consteval— detectează dacă te afli într-un context de evaluare constantă
constexpr Funcții
O funcție constexpr poate fi evaluată în timpul compilării sau în timpul execuției. Dacă este apelat cu argumente de compilare într-un context care necesită o constantă (argument șablon, variabilăconstexpr,static_assert, dimensiunea matricei), compilatorul o evaluează la momentul compilării. În caz contrar, rulează în timpul rulării ca o funcție normală.
#include <iostream>
#include <array>
#include <cstdint>
constexpr int factorial(int n) {
int result = 1;
for (int i = 2; i <= n; ++i) {
result *= i;
}
return result;
}
constexpr int fibonacci(int n) {
if (n <= 1) return n;
int a = 0, b = 1;
for (int i = 2; i <= n; ++i) {
int next = a + b;
a = b;
b = next;
}
return b;
}
int main() {
// Compile-time evaluation: used in constexpr context
constexpr int fact10 = factorial(10); // computed at compile time
static_assert(fact10 == 3628800);
constexpr int fib10 = fibonacci(10); // computed at compile time
static_assert(fib10 == 55);
// Compile-time array sizes
std::array<int, factorial(5)> arr{}; // array of 120 elements
// Runtime evaluation: argument not constexpr
int n;
std::cin >> n;
std::cout << "factorial(" << n << ") = " << factorial(n) << '\n';
// Same function, now runs at runtime
}consteval și constinit (C++20)
consteval marchează o funcție care trebuie să fie evaluată în timpul compilării - apelarea acesteia cu valori de rulare este o eroare de compilare. Aceasta se numește „funcție imediată”. constinit asigură că o variabilă cu durată de stocare statică sau locală a firului de execuție este inițializată în timpul compilării, prevenind „eșecul ordinii de inițializare statică” — dar, spre deosebire deconstexpr, variabila poate fi modificată după inițializare.
#include <iostream>
// consteval: MUST be evaluated at compile time
consteval int square(int x) {
return x * x;
}
// constinit: must be initialized at compile time,
// but can be modified at runtime
constinit int global_value = square(7); // 49, computed at compile time
// constexpr variable: initialized at compile time AND immutable
constexpr int fixed_value = square(10); // 100, compile time, cannot change
int main() {
constexpr int a = square(5); // OK: compile-time context
static_assert(a == 25);
// int x = 5;
// int b = square(x); // ERROR: consteval requires compile-time args
// constinit variable can be modified after initialization
global_value = 100; // OK: runtime modification allowed
std::cout << global_value << '\n'; // 100
// fixed_value = 200; // ERROR: constexpr variable is const
}dacă constexpr în Practică
if constexpr (C++17) evaluează o condiție în timpul compilării și elimină ramura nepreluată în întregime. Ramura eliminată nu este instanțiată — nu trebuie să fie cod valid pentru argumentele șablonului date. Acest lucru este fundamental diferit de unifobișnuit, unde ambele ramuri trebuie să fie valide.
Reguli cheie:
1. Condiția trebuie să fie o expresie constantă în timp de compilare.
2. Într-un șablon, ramura eliminată este nu instanțiată pentru argumentele șablonului curent.
3. În afara unui șablon, ambele ramuri trebuie să fie încă valide sintactic (pur și simplu nu se vor executa).
4.if constexprpoate apărea în funcțiile obișnuite, nu doar în șabloane, dar eliminarea se aplică numai în instanțierea șablonului.
5. Fiecare ramurăif constexprare propriul domeniu de aplicare - variabilele declarate într-o ramură nu sunt vizibile în cealaltă.
Containere constexpr (C++20)
C++20 allowsstd::vectorandstd::stringto be used in constexpr contexts. You can build, manipulate, and query containers at compile time — a massive expansion of compile-time programming capabilities. The constraint: transient allocation — memory allocated during compile-time evaluation must be freed before the constexpr evaluation ends.
#include <vector>
#include <string>
#include <algorithm>
#include <numeric>
#include <array>
// Build and process a vector at compile time (C++20)
constexpr int sum_of_squares(int n) {
std::vector<int> v;
for (int i = 1; i <= n; ++i) {
v.push_back(i * i);
}
return std::accumulate(v.begin(), v.end(), 0);
// vector memory freed here — transient allocation
}
// Compile-time string processing
constexpr std::size_t count_vowels(std::string_view sv) {
std::size_t count = 0;
for (char c : sv) {
if (c == 'a' || c == 'e' || c == 'i' || c == 'o' || c == 'u' ||
c == 'A' || c == 'E' || c == 'I' || c == 'O' || c == 'U') {
++count;
}
}
return count;
}
// Generate a lookup table at compile time
template <std::size_t N>
constexpr auto generate_squares() {
std::array<int, N> result{};
for (std::size_t i = 0; i < N; ++i) {
result[i] = static_cast<int>(i * i);
}
return result;
}
static_assert(sum_of_squares(5) == 55); // 1+4+9+16+25
static_assert(count_vowels("Hello World") == 3);
constexpr auto squares = generate_squares<10>();
static_assert(squares[7] == 49);Practical Compile-Time Programming
Compile-time computation is not just a parlor trick. Real-world uses include: building lookup tables embedded in the binary, validating configurations at compile time, computing hashes for compile-time string matching, and generating optimized code paths.
#include <array>
#include <cstdint>
#include <stdexcept>
// Compile-time CRC32 table generation
constexpr auto generate_crc32_table() {
std::array<uint32_t, 256> table{};
for (uint32_t i = 0; i < 256; ++i) {
uint32_t crc = i;
for (int j = 0; j < 8; ++j) {
crc = (crc >> 1) ^ (0xEDB88320 * (crc & 1));
}
table[i] = crc;
}
return table;
}
constexpr auto crc32_table = generate_crc32_table();
// Compile-time configuration validation
struct Config {
int port;
int max_connections;
int timeout_ms;
};
consteval Config validated_config(int port, int max_conn, int timeout) {
if (port < 1 || port > 65535)
throw "Port out of range"; // compile error if invalid!
if (max_conn < 1 || max_conn > 10000)
throw "Invalid max connections";
if (timeout < 0)
throw "Negative timeout";
return {port, max_conn, timeout};
}
// This validates at compile time — typo or bad value = build fails
constexpr auto config = validated_config(8080, 1000, 5000); // OK
// constexpr auto bad = validated_config(99999, 1000, 5000); // ERROR!Common mistakes with compile-time programming:
1. Assumingconstexprmeans compile-time only — aconstexprfunction can run at runtime too. Onlyconstevalguarantees compile-time evaluation.
2. Transient allocation — in C++20 constexpr contexts, all memory allocated during evaluation must be freed before the evaluation completes. You cannot store aconstexpr std::vectoras a global (its heap memory would persist).
3. Not all functions are constexpr-safe — I/O, thread creation,reinterpret_cast, and inline assembly cannot appear in constexpr contexts.
4. Overusing constexpr — computing everything at compile time increases build times. Reserve it for values that genuinely benefit from being compile-time constants.
constexprfunctions run at compile time OR runtime depending on contextconsteval(C++20) forces compile-time-only evaluation — runtime arguments are compile errorsconstinit(C++20) ensures compile-time initialization of static/thread-local variables but allows runtime modificationif constexprdiscards the untaken branch, which need not be valid for the given template arguments- C++20 allows
std::vectorandstd::stringin constexpr contexts with transient allocation - Practical uses include lookup table generation, configuration validation, and compile-time hashing
Test — Verifică-ți cunoștințele
(15 XP)1. What is the difference between`constexpr`and`consteval`?
2. Can you have a`constexpr std::vector<int>`as a global variable in C++20?
3. What does`constinit`guarantee?