DMarlon Studio

Lección 19 · 19 de 20

constexpr y static_assert · C++

Calcule al compilar con constexpr, compruebe condiciones con static_assert y elija código con if constexpr: tamaños, proporciones y tablas en C++.

C++ · constexpr y static_assert en DMarlon Studio: el código del ejemplo y su salida en la terminal

Una función `constexpr` se puede evaluar al compilar cuando sus argumentos se conocen de antemano. mebibytes(64) y gcd(1920, 1080) no se calculan al ejecutar el programa: el ejecutable ya lleva los resultados. gcd (el máximo común divisor, por el algoritmo de Euclides) es recursiva, y aun así vale.

Desde C++17, una función constexpr puede tener variables y bucles: makeSquares rellena un std::array con los cuadrados del 0 al 9, y squares es una tabla calculada al compilar. Las variables constexpr, como width o cacheSize, son constantes cuyo valor conoce el compilador.

`static_assert` es una comprobación que hace el compilador: si la condición es falsa, el programa no compila y se muestra el mensaje. Aquí garantiza que la pantalla es 16:9, que 64 MiB son 67 108 864 bytes y que la tabla es correcta. Desde C++17 el mensaje es opcional, como en el tercero.

La misma función constexpr sirve en tiempo de ejecución: n no se conoce al compilar, así que gcd(n, 36) se calcula al ejecutar, como cualquier otra función.

`if constexpr` elige una rama al compilar. En la plantilla describe, std::is_integral_v<T> (de <type_traits>) dice si T es un tipo entero: para int solo se compila la primera rama y para double, la segunda.

Código · main.cpp

#include <array>#include <iostream>#include <type_traits>// constexpr: the compiler can work it out while compiling, if the inputs are known.constexpr unsigned long long mebibytes(unsigned long long n) { return n * 1024 * 1024; }constexpr int gcd(int a, int b) { return b == 0 ? a : gcd(b, a % b); }constexpr std::array<int, 10> makeSquares() {      // a table built at compile time    std::array<int, 10> t{};    for (int i = 0; i < 10; ++i) t[i] = i * i;    return t;}constexpr auto squares = makeSquares();constexpr int width = 1920, height = 1080;constexpr unsigned long long cacheSize = mebibytes(64);// static_assert: a check done by the compiler. If it fails, nothing compiles.static_assert(width * 9 == height * 16, "the screen must be 16:9");static_assert(cacheSize == 67108864, "64 MiB in bytes");static_assert(squares[7] == 49 && sizeof(long long) >= 8);template <typename T>void describe(T value) {           // if constexpr: only the matching branch is compiled    if constexpr (std::is_integral_v<T>) std::cout << value << " is a whole number\n";    else std::cout << value << " is not a whole number\n";}int main() {    constexpr int g = gcd(width, height);    std::cout << "Aspect ratio " << width / g << ":" << height / g << ", cache " << cacheSize << " bytes\n";    std::cout << "Square of 7 from the table: " << squares[7] << "\n";    int n = 84;                    // known only at run time: the same function still works    std::cout << "gcd(" << n << ", 36) = " << gcd(n, 36) << "\n";    describe(42); describe(2.5);}

Salida · c++ -std=c++17 main.cpp -o main && ./main

Aspect ratio 16:9, cache 67108864 bytes
Square of 7 from the table: 49
gcd(84, 36) = 12
42 is a whole number
2.5 is not a whole number

Para recordar

  • constexpr permite calcular al compilar si los datos se conocen.
  • Una función constexpr también funciona con datos del tiempo de ejecución.
  • static_assert(condición, "mensaje") detiene la compilación si falla.
  • if constexpr descarta al compilar la rama que no corresponde.
  • Lo que se calcula al compilar no cuesta nada al ejecutar.

Ejercicio

Cambie height a 1200 y compile: lea el mensaje del static_assert. Después, escriba una función constexpr factorial(int n) y compruebe con static_assert que factorial(5) == 120.