Constant Expressions
A constant expression is an expression that must be evaluated at compile-time. They are the backbone of compile-time evaluation in C++.
To be able to be evaluated at compile-time, all of it's parts need to be evaluable at compile-time. Constant expression can contain the following:
- Literals
- Most operators with constant expression operands
- Const integral variables (historical, constexpr variables are preffered)
- constexpr variables
- constexpr function calls with constant expression arguments
- non-type template parameters
- enumerators
- type traits
- constexpr lambda expressions
Even if the return value of a normal (non-constexpr) function is constant, it's never considered a constant expression because functions execute at runtime.
1.1. Constexpr Variables and Functions
When using a const variable, we aren't sure if it's a constant expression or not. For this, we have the constexpr keyword, which guarantees the variable to be a compile-time constant.
With the same keyword we can create constexpr functions. They must evaluate at compile-time when called in a constant expression, otherwise they can also be evaluated at runtime.
1.2. Why the fuck?
For what could you use an expression that is guaranteed to be evaluated at compile time?
- some types require compile-time values (e.g. array sizes)
- compile-time "if" statements: the branch that evaluates to false is completely discarded by the compiler.
- string hashing for switch statements: switch only works with integers, not strings. This has no performance penalty, compared to runtime solutions.
- pre-computed lookup tables: if you use heavy math, you can generate an array of precalculated values, and your code just grabs the answer. Great for performance.
- zero-cost compile-time validations: in embedded/safety systems like aerospace or meical software, you can't risk the program starting up with bad configuration. This way you can parse and validate before the binary is even generated.
- move global initialization out of startup time: if you have a complex global object it can slow down startup, by creating it at compile-time, when launching the app it gets flashed to memory instantly.