Lesson 7 of 8
Two keywords that look similar and answer different questions. const asks can this change?
constexpr asks when is this known?
const: this will not change
#include <iostream>
int main() {
const int max_users = 100;
int current = 42;
std::cout << max_users << " " << current << "\n";
current = 43;
std::cout << current << "\n";
return 0;
}
100 42
43
Try to modify a const and the compiler stops you:
// WILL NOT COMPILE
int main() {
const int max_users = 100;
max_users = 200;
return 0;
}
error: assignment of read-only variable 'max_users'
A const must be initialized where it's declared — there's no later opportunity.
Why bother?
Three real payoffs, in increasing order of importance:
It documents intent. const int max_retries = 3; says this is a fixed policy, not a variable
someone will tune at run time. The next reader knows without checking.
It catches mistakes. The classic is = where you meant ==:
// WILL NOT COMPILE
int main() {
const int limit = 10;
int value = 5;
if (limit = value) { // assignment, not comparison — caught because limit is const
return 1;
}
return 0;
}
Without const that compiles fine, silently assigns, and behaves nothing like you intended.
It enables optimization. A compiler that knows a value can't change can make assumptions it otherwise couldn't.
Named constants beat magic numbers
#include <iostream>
int main() {
const double tax_rate = 0.10;
const int free_shipping_threshold = 50;
double subtotal = 60.0;
double total = subtotal * (1.0 + tax_rate);
bool free_shipping = subtotal >= free_shipping_threshold;
std::cout << total << "\n";
std::cout << std::boolalpha << free_shipping << "\n";
return 0;
}
66
true
Compare that to subtotal * 1.10 scattered through a codebase. When the tax rate changes you edit
one line instead of hunting for every 1.10 — and you avoid the ones that were coincidentally
1.10 for unrelated reasons.
constexpr: known at compile time
constexpr is a stronger promise: not just "won't change," but "the value is computable while
compiling, before the program runs."
#include <iostream>
int main() {
constexpr int buffer_size = 1024;
constexpr int half = buffer_size / 2;
int array[buffer_size];
array[0] = 7;
std::cout << buffer_size << " " << half << " " << array[0] << "\n";
std::cout << sizeof(array) / sizeof(array[0]) << "\n";
return 0;
}
1024 512 7
1024
half is computed by the compiler; the running program contains 512 as a literal, with no
division performed at run time.
The array size is where the difference becomes visible. C-style array sizes must be known at
compile time — a constexpr works there and an ordinary variable doesn't:
int size = 10; // a run-time value
int array[size]; // not standard C++
Standard C++ forbids this. GCC and Clang nevertheless accept it as a compiler extension
(inherited from C, where it's legal), so it may well compile silently on your machine — add
-Wpedantic and you'll get warning: ISO C++ forbids variable length array.
Don't rely on it. Use std::vector (lesson 6.3) when the size isn't known until run time; that's
the portable answer and it's what you'd want anyway.
constexpr functions
Functions can be constexpr too, meaning "callable at compile time when the arguments allow":
#include <iostream>
constexpr int square(int n) {
return n * n;
}
int main() {
constexpr int compile_time = square(12);
int runtime_input = 7;
int run_time = square(runtime_input);
std::cout << compile_time << " " << run_time << "\n";
int grid[square(4)];
grid[0] = 1;
std::cout << sizeof(grid) / sizeof(grid[0]) << "\n";
return 0;
}
144 49
16
The same function serves both: called with a constant it's evaluated during compilation; called
with a run-time value it runs normally. square(4) as an array size works precisely because the
compiler can evaluate it.
Module 22 goes much deeper into compile-time computation. For now, the useful habit is simply to
mark genuinely fixed values constexpr rather than const.
Choosing between them
| | const | constexpr |
| --- | --- | --- |
| Means | won't change after initialization | value known at compile time |
| Initializer | may be computed at run time | must be a compile-time constant |
| Array sizes | no | yes |
| Use when | a value shouldn't be modified | a value is genuinely fixed in the source |
constexpr implies const — a constexpr value can't change either. So:
constexpr int max_retries = 3; // fixed in source → constexpr
const int user_limit = readConfigValue(); // fixed once known → const
The rule of thumb: constexpr if you can, const if you can't, neither only if the value
genuinely varies.
Try it
Write a program with a constexpr function cube(int), use it to size a C-style array, and
also call it with a value read from a normal int variable. Print both results.
Checkpoint
constmeans the value cannot be modified after initialization; it must be initialized at declaration.constdocuments intent, catches=-for-==mistakes, and helps the optimizer.- Named constants beat magic numbers scattered through code.
constexprmeans the value is computed at compile time — required for C-style array sizes.constexprfunctions can run at compile time or run time depending on their arguments.- Prefer
constexprwhere possible,constotherwise.
Questions about this lesson
Log in to ask a question.