C++ const, constexpr and auto Explained
Understand C++ const, constexpr and auto with separate mental models for immutability, constant expressions and type deduction. Repair runtime constexpr and integer-division mistakes.
What you will learn
- Use const to express a stable value.
- Distinguish runtime const values from constant expressions.
- Use constexpr for suitable compile-time constants.
- Predict auto from its initializer rather than its name.
- Combine const or constexpr with deduction intentionally.
Three keywords answer three different questions
const, constexpr and auto often appear in the same declaration, but they do not mean the same thing. Ask whether an object should change, whether its initializer can satisfy a constant-expression requirement, and how its type is determined. const addresses modification; constexpr imposes constant-expression requirements for the variables introduced here; auto asks the compiler to deduce a type. Keeping those questions separate prevents a short declaration from becoming an unexplained incantation.
For a small immutable setting whose numeric meaning is obvious, constexpr int retryLimit{3} makes both type and compile-time availability clear. For a result produced while a program runs, const int measuredCount{expression} can describe a value you do not intend to change afterward. auto is useful when an initializer communicates a type clearly or when repeating a complex type would obscure the calculation. Neither spelling replaces the need to understand that initializer.
These examples concern local value objects, not every advanced const rule. Pointers, references, mutable members and overload resolution add details in later lessons. Do not assume a const declaration makes all reachable objects immutable or secures a program against malicious input. It communicates and enforces particular language constraints. Use the smallest claim supported by the actual object and access path rather than treating a keyword as a general security feature.
A const value can be determined at runtime
const int total{12} prevents ordinary assignment to total. If a value is initialized from a runtime measurement, it can still be const: after initialization, this part of the program does not intend to replace it. That stability helps a reader distinguish configuration from changing state. It also catches accidental assignments during maintenance. Do not remove const merely because a diagnostic complains; first check whether the assignment contradicts the requirement.
Some const integral objects initialized by a constant expression can themselves participate in constant expressions. Therefore 'const always means runtime' is incorrect. The useful beginner distinction is that const alone does not require every initializer to be a constant expression. A runtime-dependent initializer is permitted for an ordinary const variable, while the constexpr declaration used here rejects one. Explain the guarantee of the declaration rather than guessing when an optimizer will physically perform a calculation.
A copied const object does not automatically make the copy const. With const int setting{4}; auto copy = setting;, ordinary value deduction gives copy type int. You may then change copy without modifying setting. Use const auto copy = setting; if the copy should also remain stable. This lesson deliberately avoids reference deduction: later borrowing lessons explain when a declaration binds to an existing object instead of making a new value.
constexpr is a checked requirement, not a speed switch
For these scalar variable examples, constexpr requires a suitable constant-expression initializer and makes the object const. constexpr int days{7} is a named compile-time constant. constexpr int hours{days * 24} composes another one. If the initializer depends on input obtained while the program runs, that requirement cannot be satisfied. Calling a value a setting does not change the expression's properties; the compiler checks the initializer rather than your chosen identifier.
static_assert can check a constant-expression condition during translation. It is not an if statement and does not print an error at runtime. A failing static assertion prevents the build. Use it for a genuinely static invariant, such as a fixed number of hours matching your stated configuration. It cannot validate a ticket count entered tomorrow. Runtime inputs need runtime checks, which the conditions and input lessons develop separately.
Do not claim constexpr always makes an application faster. The optimizer can evaluate other expressions early when allowed, and the language guarantee is more specific than a general performance promise. Functions declared constexpr have additional rules and can sometimes run at runtime; function definitions are covered later. Here focus on variable declarations, readable constants and build-time requirements. Avoid expanding a small configuration example into an unsupported claim about every expression or library facility.
Stable configuration with a checked constant
static_assert is checked at build time. The output statements still belong to program execution.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Hours: 168
After editing inputs, calculate a new prediction; this reference describes only the starter.
Repair a runtime-dependent constexpr initializer
Intentional compile failure. argc is the runtime argument count, even if a particular launch is predictable. Replace constexpr with const to keep the value stable without requiring a constant expression.
Predict the output, edit the source, then select Run code.
auto follows the expression—not the variable's purpose
auto count = 5 deduces int; auto ratio = 5.0 deduces double. auto average = 7 / 2 also deduces int because the division is integer arithmetic. Naming it average does not request a fractional result. Repair the operands before deciding whether auto or an explicit destination type is clearer. An explicit double destination would also fail to recover a fraction already discarded, so this is not simply an argument against deduction.
The value's type is fixed after deduction. auto does not produce a dynamically typed variable that changes type whenever assigned a different value. A later assignment must obey the normal conversion rules of the deduced type. Prefer initializer forms you can explain: auto x = 3 and auto x{3} deduce int here, but auto x = {3} creates an initializer_list<int>. The similar punctuation carries a different meaning, so do not mechanically swap the forms.
A string literal does not become std::string merely because auto appears. auto label = "ready" gives a pointer to const character data in this ordinary declaration. Use std::string label{"ready"} or auto label = std::string{"ready"} when you need an owning string object. Pointer syntax is introduced later; for now retain an explicit std::string where text ownership is part of the lesson. Type deduction removes repetition, not the obligation to know what type was selected.
Trace the deduced numeric types
The static assertions verify types; the two printed results reveal the calculation. Change the operands, not just the variable names.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Integer: 3
Ratio: 3.5
After editing inputs, calculate a new prediction; this reference describes only the starter.
Choose a declaration a future reader can explain
A useful declaration records a real design decision. Prefer const when mutation is unnecessary; use constexpr when the value intentionally meets a constant-expression requirement; choose auto when the initializer makes the selected type understandable. It is reasonable to write explicit numeric types at an external boundary or where units and conversions need emphasis. There is no requirement to shorten every declaration or to reject deduction everywhere.
Review a change by asking what happens if the initializer changes. Replacing an integer literal with a floating-point literal may change an auto variable's type and downstream operations. Replacing a constant initializer with input may invalidate constexpr. Introducing an update may contradict const. Keep these consequences visible in tests and explanations. A declaration that compiles today is not automatically a sensible interface for every future input source.
Build and run the local examples
Save one complete example or lab solution as main.cpp. Choose your installed toolchain; run only after a successful build. Input examples wait for text in your terminal. The browser teaching interpreter does not support these full-language features.
macOS / Clang:
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson
Linux / GCC:
g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson
Windows Developer Command Prompt / MSVC:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Represent a fixed page size and a stable runtime result. Calculate how many complete pages and leftover records exist for thirteen records.
Acceptance checklist
- Use constexpr int pageSize{5}.
- Use const int recordCount{13} as the fixed stand-in for a runtime observation.
- Use const auto for quotient and remainder; explain their deduced integer types.
- Check pageSize at compile time using static_assert.
- Print Full pages: 2 and Leftover: 3 on separate lines.
Need a hint?
- Integer division answers how many complete groups fit.
- Remainder reports records outside those complete groups.
- A fixed stand-in can be const even though later real input would be runtime-dependent.
Write and run your lab
Build your own solution before revealing the reference. Use the input field for normal, boundary and invalid cases; this runner executes your program but does not grade it.
Predict the output, edit the source, then select Run code.
Your lab evidence and notes
Record predictions, actual output, commands and the cause of each repair. Code is plain text: this scratchpad does not execute or grade C++.
Loading local notes…
Review your acceptance checklist and quiz explanations before marking this lesson complete. This is self-review, not a grade or certification.
Reveal the explained lab solution
Reference lab solution
Compare the reference with your own reasoning. Running this solution does not grade or complete the lesson.
Predict the output, edit the source, then select Run code.
Expected output
Full pages: 2
Leftover: 3
pageSize is a named compile-time setting; static_assert rejects a nonpositive compile-time size.
recordCount is stable in this calculation. The example does not read a database or claim runtime input has already been implemented.
Both operands are int, so const auto produces const int results. With zero records the two output values become zero.
Review before moving on
Explain the lesson's objectives in your own words, compare your lab evidence with the acceptance checklist, and keep the first failing case alongside your repair. Completing a page is not proof that every C++ feature or input is supported.
- Use const to express a stable value.
- Distinguish runtime const values from constant expressions.
- Use constexpr for suitable compile-time constants.
- Predict auto from its initializer rather than its name.
- Combine const or constexpr with deduction intentionally.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.