Constants and constexpr
Values that shouldn't change deserve to be marked as such. C++ has const for read-only values and constexpr for values — and functions — the compiler can evaluate before the program even runs.
const
int main() { const double vat_rate = 0.20; vat_rate = 0.25; return 0; }
main.cpp:3:14: error: cannot assign to variable 'vat_rate' with const-qualified type 'const double'
A const variable must be initialised and can never be changed. Its value can come from anything, including run-time input.
constexpr
constexpr goes further: the value must be known at compile time. That lets you use it anywhere a compile-time constant is required, such as array sizes and template arguments:
#include <array> #include <iostream> constexpr int board_size = 8; constexpr double pi = 3.14159265358979; int main() { std::array<int, board_size> row{}; constexpr double area = pi * 2.0 * 2.0; std::cout << row.size() << " squares, circle area " << area << '\n'; }
8 squares, circle area 12.5664
Prefer constexpr for true constants and const for values computed at run time that shouldn't change afterwards. Both beat #define macros, which have no type and ignore scope.
constexpr functions
A constexpr function can run at compile time when its arguments are constants, and at run time otherwise:
#include <iostream> constexpr long long factorial(int n) { long long result = 1; for (int i = 2; i <= n; ++i) result *= i; return result; } int main() { constexpr long long f10 = factorial(10); // computed by the compiler static_assert(f10 == 3628800); // checked at compile time int n = 15; std::cout << f10 << ' ' << factorial(n) << '\n'; // run time }
3628800 1307674368000
static_assert checks a condition while compiling — a failed one is a compile error, so the bug never reaches a running program.
consteval
C++20's consteval marks a function that must run at compile time. Calling it with a run-time value is an error:
consteval int square(int x) { return x * x; } int main() { constexpr int a = square(12); // fine int n = 5; int b = square(n); // error: n isn't a constant return a + b; }
main.cpp:6:13: error: call to consteval function 'square' is not a constant expression
Enumerations for related constants
For a fixed set of named choices, an enum class is better than a group of integer constants (lesson 20 covers it fully):
#include <iostream> enum class Size { small, medium, large }; int main() { Size s = Size::medium; if (s == Size::medium) std::cout << "medium selected\n"; }
medium selected
Avoid magic numbers
A literal like 86400 in the middle of code forces the reader to guess its meaning. A named constant documents it and lets you change it in one place:
#include <iostream> constexpr int seconds_per_minute = 60; constexpr int seconds_per_hour = 60 * seconds_per_minute; constexpr int seconds_per_day = 24 * seconds_per_hour; int main() { int t = 200'000; std::cout << t / seconds_per_day << " d " << t % seconds_per_day / seconds_per_hour << " h " << t % seconds_per_hour / seconds_per_minute << " min " << t % seconds_per_minute << " s\n"; }
2 d 7 h 33 min 20 s
Try it yourself
Write a constexpr function that converts Celsius to Fahrenheit, and use static_assert to check that 100 °C is 212 °F.
-10 C = 14 F 0 C = 32 F 21.5 C = 70.7 F 37 C = 98.6 F