C++ Const Member Functions and Composition
Learn C++ const member functions and composition with owning line items, const-correct total queries, bounded integer cents and a two-line order lab.
What you will learn
- Distinguish a const object, const member query and immutable member declaration.
- Compose an Order from owned LineItem values without inheritance.
- Make read-only queries callable across a const object graph.
- Bound multiplication and addition with explicit price and quantity contracts.
- Test zero-value and maximum-value composed reports.
A trailing const belongs to the member function's object access
A total query should be callable when an order is only available through a const reference. Write total() const to express that its implementation does not modify ordinary non-mutable members through the current object. The trailing qualifier is different from returning a const value, and different from making a member variable const. Reading those declarations as interchangeable obscures which operation the compiler is actually allowed to call.
A const object can call appropriately qualified const members, but not an ordinary non-const member merely because its current body happens to perform only arithmetic. The signature is the caller-visible promise. Put the qualifier on observing functions when that is their intended contract. Do not use const_cast to get around a missing qualifier in a teaching example; repair the interface and let the compiler check the intended read-only access path.
Const correctness is not a universal purity guarantee. A const method could access globals, write through a pointer member to a separate object, or modify a mutable cache. Our small totals do none of those things: they read owned numeric fields and return a computed integer value. Keep that concrete behavior separate from broader claims such as thread safety, determinism of arbitrary methods, or transitive immutability of everything an object might reference.

Composition models a has-a relationship with ordinary values
An order has line items; it is not a specialized kind of line item. Represent that relationship with members rather than inheritance. In this deliberately small model, Order owns exactly two LineItem objects called first_ and second_. Their lifetimes are tied to the enclosing order's lifetime. No separate heap allocation, shared pointer or external database is required to represent this whole-part relationship in a single-file example.
A variable-size order could later store a vector of line-item values, but changing the collection shape is not necessary to learn composition. A fixed two-part model makes construction and query dependencies visible while reusing familiar values. State the limitation honestly: Lines: 2 is a property of this laboratory's representation, not a live commerce statistic or a claim that the code already supports a production shopping cart.
Each LineItem constructor protects its own unit-price and quantity bounds. The Order constructor accepts complete line-item values and initializes its members from them. This means the aggregate total can rely on those existing local guarantees rather than re-parsing original text. Composition becomes useful when each part offers an interface the larger type can use without learning how its representation was validated or how future internal changes are implemented.
Query a const order through const line-item methods
All arithmetic uses already validated values. The returned total is in integer cents, not formatted currency or a verified payment.
Predict the output, edit the source, then select Run code.
Compare with the starter's expected output
Total cents: 2600
After editing inputs, calculate a new prediction; this reference describes only the starter.
Read-only access must work all the way through the query
Inside Order::total() const, ordinary owned members are observed through a const access path. Calling LineItem::total() therefore requires that part's query to be const as well. Qualifying only the outer query is insufficient when its implementation calls a non-const member on an owned part. The compiler error identifies a break in the observation contract, not evidence that composition itself requires making all objects mutable.
The intentional failure uses a minimal Counter with a read-only-looking count() body but no trailing qualifier. Calling it through a const Counter fails. Repair the signature to count() const and rerun, rather than removing const from the caller to hide the mismatch. Then compare with a legitimate mutation such as increment(): that operation should remain non-const because its purpose is to change ordinary state.
Do not confuse const member functions with const data members. Making a field const changes what assignments and generated special operations a class can support. It is not necessary just to offer a read-only view to a caller. Our numeric members remain ordinary private integers; access sections and the absence of mutation methods determine the available interface, while const query qualifiers allow sensible use from const objects and references.
Repair a query signature rather than weakening its caller
Intentional compile error. Add a trailing const to count(), keeping the const object intact.
Predict the output, edit the source, then select Run code.
Define arithmetic bounds and return observations by value
The lab allows each unit price to range from zero through ten thousand cents and each quantity from zero through one hundred. One line total is therefore at most one million cents, and the two-line order total at most two million. Use long long for the result and start multiplication with 1LL so the expression uses that type from the beginning. The explicit contract is still important; a wider integer is not a proof against arbitrary unbounded inputs.
Integer cents avoid introducing floating-point display decisions into this lesson, but they do not constitute a complete money type. There is no currency conversion, tax calculation, decimal parser, refund policy or payment integration here. If a program later accepts a larger number of lines or quantities, re-evaluate the sum's range rather than assuming this two-line proof applies unchanged. Arithmetic guarantees depend on both data bounds and collection shape.
Return numeric totals by value and avoid exposing mutable references to the owned parts merely for convenience. A const reference getter, if provided, would still borrow from an owner whose lifetime matters; our total query does not need one. The smaller interface lets a caller ask the question it needs without taking responsibility for internal member lifetimes or bypassing the line-item constructor's rules through an unintended mutation path.
Validate the input boundary, then test the composed value
Read four complete integer lines in order: first unit price, first quantity, second unit price and second quantity. Missing or malformed required lines print Invalid input and return status one before construction. After successful parsing, build two line items and the const order inside a narrow try block. A domain-bound failure prints Invalid item and returns one; a successful report prints Lines: 2 followed by Total cents and the computed result.
Test zero price, zero quantity, both lines at their maximum, negative parameters and one-past-maximum parameters in every position. Include a malformed final token and a valid final line without a newline. A zero-quantity line remains an owned line item under this representation; it contributes zero but does not disappear from the declared count of two. Additional input after the four required lines is outside this one-report protocol and remains unread.
For a repair exercise, remove the trailing const from the inner total query and explain why the outer const order can no longer call it. Restore the qualifier, then verify that repeated total queries yield the same value without changing the parts. The purpose is to see composition as a chain of compatible contracts. It is not to memorize a rule that every method should be const or to treat a compiler error as a reason to discard useful access restrictions.
Build and investigate locally
Complete single-file programs use the compiler-backed editor when its sandbox is available. Otherwise save one program as main.cpp and build with your installed C++20 compiler. Expected output is a comparison target, not a substitute for execution.
macOS / Linux (choose your compiler):
clang++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
# Or: g++ -std=c++20 -Wall -Wextra -Wpedantic main.cpp -o lesson
./lesson
Windows Developer Command Prompt:
cl /std:c++20 /EHsc /W4 main.cpp /Fe:lesson.exe
lesson.exeIndependent lab
Build a const two-line order whose observing methods compose without exposing writable internal parts.
Acceptance checklist
- Read four whole integer lines: unit cents and quantity for each of two line items.
- Accept unit cents in 0..10000 and quantity in 0..100, enforced by LineItem construction.
- Distinguish malformed input as Invalid input from domain failure as Invalid item, both with status 1.
- Own both LineItem values in Order and make each total query const.
- Print Lines: 2 and Total cents using bounded integer arithmetic; zero quantities contribute zero.
Need a hint?
- The inner query must be callable when the outer const method observes its member.
- Use 1LL before multiplication rather than converting an already evaluated int product later.
- Validate by constructing parts before reporting; no partial success output should precede a rejected part.
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
Lines: 2
Total cents: 2600
The numeric parser and constructors establish different boundaries; neither phase prints an accepted order before all parts exist.
Order owns complete values and observes them through const-qualified methods, expressing composition without hierarchy or shared ownership.
The allowed ranges bound both multiplication and the two-part sum; integer cents are a teaching unit, not a payment or full financial model.
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.
- Distinguish a const object, const member query and immutable member declaration.
- Compose an Order from owned LineItem values without inheritance.
- Make read-only queries callable across a const object graph.
- Bound multiplication and addition with explicit price and quantity contracts.
- Test zero-value and maximum-value composed reports.
References and further reading
Original examples and exercises accompany these primary references. Tool-specific diagnostics and support depend on your installed version.