DMarlon Studio

Lesson 19 · 19 of 20

constexpr and static_assert · C++

Compute at compile time with constexpr, check conditions with static_assert and pick code with if constexpr: sizes, ratios and tables in C++.

C++ · constexpr and static_assert in DMarlon Studio: the example's code and its output in the terminal

A `constexpr` function can be evaluated at compile time when its arguments are known in advance. mebibytes(64) and gcd(1920, 1080) are not worked out when the program runs: the executable already contains the results. gcd (the greatest common divisor, by Euclid's algorithm) is recursive, and that is fine.

Since C++17, a constexpr function can have variables and loops: makeSquares fills a std::array with the squares of 0 to 9, so squares is a table built at compile time. constexpr variables such as width or cacheSize are constants whose value the compiler knows.

`static_assert` is a check made by the compiler: if the condition is false, the program does not compile and the message is shown. Here it guarantees that the screen is 16:9, that 64 MiB is 67,108,864 bytes and that the table is right. Since C++17 the message is optional, as in the third one.

The same constexpr function works at run time too: n is not known while compiling, so gcd(n, 36) is computed when the program runs, like any other function.

`if constexpr` picks a branch at compile time. In the describe template, std::is_integral_v<T> (from <type_traits>) says whether T is an integer type: for int only the first branch is compiled, and for double only the second.

Code · 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);}

Output · 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

Key points

  • constexpr lets values be computed at compile time when the inputs are known.
  • A constexpr function also works with run-time data.
  • static_assert(condition, "message") stops the build if it fails.
  • if constexpr throws away the branch that does not apply, at compile time.
  • Work done at compile time costs nothing at run time.

Exercise

Change height to 1200 and compile: read the static_assert message. Then write a constexpr function factorial(int n) and check with static_assert that factorial(5) == 120.