Inventory Ledger
You are building the Inventory Ledger — a small library that tracks game-shop
items by rarity tier. An item has a name, a Rarity (Common, Rare, or Epic), a
stock quantity, and a base unit value. The library computes how much a full stock
is worth, restocks shelves, and answers catalogue-identity queries.
The design enforces the two central lessons of Chapter 13:
-
Scoped enumerations (
enum class) —Rarityis anenum class. Its enumerators live inside its own scope (Rarity::Common, notCommon), it will not silently convert toint, and it switches cleanly on all three values. You will implement the enum-to-string and enum-to-multiplier helpers that every real codebase needs once it adopts scoped enums. -
Struct aggregates and their free-function API —
Itemis a data-onlystructwith four members. All the logic that operates on items lives in free functions (outside the struct), not as member functions. That boundary is deliberate: Chapter 14 introduces member functions; keeping Chapter 13 structs data-only makes the transition from "bag of data" to "object with behaviour" vivid. You will practice aggregate initialization, passing structs by const reference, returning them by value, and mutating through a non-const reference.
CS6340 connection: LLVM uses this exact pairing constantly — an enum like
Instruction::BinaryOps or AtomicOrdering combined with free helper functions
that map enumerators to strings, costs, or legality tables. The struct pattern
appears in every value-carrying utility type (e.g. DebugLoc, MaybeAlign).
Your tasks
rarityLabel— switch on a scoped enum. Return"Common","Rare","Epic", or"Unknown"for thedefaultarm. Use aswitchon the scoped enum — prefix each case label with the type name:case Rarity::Common:. Returnstd::string_view(string literals have static storage, so a non-owning view is safe and allocates nothing).rarityMultiplier— switch returning an int. MapCommon → 1,Rare → 3,Epic → 10,default → 1. Same switch pattern as Task 1. Keeping the multiplier in one helper means every caller stays in sync when the numbers change.itemWorth— const reference + member access. Computeitem.quantity × item.unitValue × rarityMultiplier(item.rarity). The parameter isconst Item&— a const reference avoids copying thestd::stringmember and promises you will not mutate the caller's data. Access members with.. Delegate torarityMultiplier; do not inline the numbers.makeItem— return a struct by value. Aggregate-initialize anItemwith the four supplied fields and return it. Member order must match the declaration inledger.h:name,rarity,quantity,unitValue. Convert thestd::string_view nameparameter tostd::stringfor the owning member.restock— mutate through a non-const reference. Addamounttoitem.quantity. Ifamountis negative, clamp it to0first (a restock function should never accidentally reduce stock). TheItem& itemparameter — a non-const reference — is what makes the mutation visible to the caller.isSameItem— compare struct fields (the capstone). Returntrueifa.name == b.nameanda.rarity == b.rarity. Quantity and unit value are irrelevant to catalogue identity. Both parameters areconst Item&. Scoped enum values compare with==directly — no cast needed.
Success criteria
- Every named Rarity round-trips through
rarityLabelandrarityMultiplier. - An out-of-range
static_cast<Rarity>(99)hits thedefaultarm — the switch must be exhaustive. itemWorthwith zero quantity returns0(the zero-edge).itemWorthwith zero unit value returns0.makeItemfields are set correctly in declaration order.restockwith a negative amount does NOT reduce stock (clamp to 0).isSameItemreturnstruefor two Items with the same name and rarity but different quantity/unitValue — those fields must be ignored.isSameItemreturnsfalsewhen only the rarity differs, and when only the name differs.
Concepts practiced
New this chapter (notes 13.1–13.12):
enum class(scoped enum) — enumerators scoped to the type, no implicit int conversion, switch withdefaultarm (notes 13.6)- Contrast with unscoped
enum— name leakage and implicit int conversion are the problemsenum classsolves (notes 13.2, 13.3) structaggregate — named members, member access with.(notes 13.7)- Aggregate initialization — brace-init fills members in declaration order (notes 13.8)
- Default member initializers — members start in a safe state (notes 13.9)
- Passing structs by
constreference — read-only, no copy (notes 13.10) - Returning structs by value — clean API, compiler handles copy elision (notes 13.10)
- Mutating through a non-const reference — the
restockpattern (notes 13.10, 13.12) - Enum-to-string helper using a
switch(notes 13.4) - Struct containing a program-defined enum member (notes 13.11)
Reused from earlier chapters:
std::stringandstd::string_view(Ch 5)constcorrectness (Ch 5)if/switch/break/default(Ch 8)- References (
&) and const references (const &) (Ch 12) static_castused only in tests for out-of-range enum edges (Ch 4/10)
Constraints
Allowed constructs:
enum class,struct, aggregate initialization with{}- Member access with
. switch/case/default/break(notes 13.4, 13.6)if/elsefor clampingconst Item&andItem¶meters (Ch 12 refs)std::stringandstd::string_view(Ch 5)- Integer arithmetic (
*,+,+=) (Ch 1) bool,int, function templates already in scope (Ch 4, 11)
Forbidden (not yet taught or explicitly out-of-scope):
- Member functions / methods inside the struct — that is Chapter 14. Keep
Itemdata-only. - Operator overloading (
operator==,operator<<) — Chapter 21. std::vector— Chapter 16.- Constructors — Chapter 14. Use aggregate initialization (
{}braces) only. - Raw
intmagic numbers inlined intoitemWorth— delegate torarityMultiplierso the mapping is centralized.
Required idioms:
- Aggregate initialization everywhere:
Item x { ... };, notItem x; x.name = ...; - Default member initializers in
ledger.h— already provided; do not remove them. const Item&for every read-only struct parameter;Item&(noconst) only when mutation is the goal.
Build & run locally
make # compile-check starter/ledger.cpp (warning-clean)
make test # grade your code -> RED until the TASK blocks are filled in
make solution # run the grader against the reference solution
make test-solution # verify the reference passes all checks
make clean # remove build artifactsmake test is the grader. make solution shows the reference output so you can
check your understanding — but peek at solution/ledger.cpp only after you have
made a genuine attempt.
Hints
Task 1 — switching on a scoped enum
switch (rarity)
{
case Rarity::Common: return "Common";
case Rarity::Rare: return "Rare";
case Rarity::Epic: return "Epic";
default: return "Unknown";
}Each case label must use the full Rarity:: prefix — that is what "scoped" means.
The default arm is essential: it satisfies -Wswitch-default and catches any
value that was created via static_cast from an integer.
Task 3 — why const reference and how to access members
The function signature is:
int itemWorth(const Item& item)const — you promise not to modify the item.
& — no copy; the parameter IS the caller's object (notes 13.10, 13.12).
Inside the body, access members with . (not ->, which is for pointers):
return item.quantity * item.unitValue * rarityMultiplier(item.rarity);Task 4 — aggregate initialization and std::string_view → std::string
Item makeItem(std::string_view name, Rarity rarity, int quantity, int unitValue)
{
return Item{ std::string{name}, rarity, quantity, unitValue };
}std::string{name} converts the non-owning view to an owning string for the
struct member — important so the member doesn't dangle (notes 13.11).
The brace list fills members in declaration order: name, rarity, quantity,
unitValue — exactly as they appear in the struct definition in ledger.h.
Task 5 — non-const reference and negative clamping
void restock(Item& item, int amount)
{
if (amount < 0)
amount = 0;
item.quantity += amount;
}The & (no const) is the entire difference between "modifies the caller's
item" and "modifies a throwaway copy that vanishes when the function returns".
Task 6 — comparing enum class values with ==
bool isSameItem(const Item& a, const Item& b)
{
return a.name == b.name && a.rarity == b.rarity;
}Scoped enum values support == directly. No static_cast<int> needed — the
whole point of enum class is that it gives you a proper type with proper
comparisons (notes 13.6).
Stretch goals
- Add a
printItemfunction that outputs"Sword [Rare] qty=2 worth=240 coins". Right now you would pass anstd::ostream& outparameter and callrarityLabel/itemWorthinside. In Chapter 21 you could overloadoperator<<sostd::cout << swordjust works. - Replace
isSameItemwithoperator==once you reach Chapter 21 operator overloading. - Add a
totalLedgerWorth(Item items[], int count)function once you learn C-style arrays (Ch 17) orstd::vector(Ch 16). The loop accumulatesitemWorth(items[i]). - Add a
Rarity fromString(std::string_view)returningstd::optional<Rarity>using thestd::optionalpattern from Chapter 12 (already in scope!). This is the exact parse-from-CLI-arg pattern shown in notes 13.4.
// Chapter 13 — Compound Types: Enums & Structs · Inventory Ledger (STARTER)
// ─────────────────────────────────────────────────────────────────────────────
// Fill in the six TASK blocks below. Each maps 1:1 to a task in the README and
// to a declaration in ../ledger.h. The function bodies currently return STUB
// VALUES so the file compiles immediately — that is why `make test` is RED right
// now. Your job is to implement the real logic and turn it GREEN.
//
// make build compile your code (should already work as-is)
// make test grade it (RED until you fill these in)
// make solution build + run the reference solution if you get stuck
// make test-solution verify the reference passes all checks
//
// SCOPE REMINDER (notes 13.1–13.12):
// • Allowed up through Ch 12: refs, const refs, static_cast, std::string,
// std::string_view, int arithmetic, if/switch/for (Ch 8), bool (Ch 4).
// • NEW this chapter: enum class, struct, aggregate init, member access with .,
// pass/return by const ref or value (notes 13.6–13.10).
// • FORBIDDEN here: member functions/methods (Ch 14), operator overloading (Ch 21),
// std::vector (Ch 16). Keep the struct DATA-ONLY. That boundary IS the lesson.
//
// KEY TERMS to keep in mind as you read the scaffolding:
// SCOPED ENUM — enum class with :: accessor (notes 13.6)
// AGGREGATE — data-only struct, initialized with {} braces (notes 13.7–13.9)
// CONST REF — const Item& avoids copying the std::string member (notes 13.10)
#include "../ledger.h"
// ─── TASK 1: rarityLabel — switch on a scoped enum ───────────────────────────
//
// Return a std::string_view label for `rarity`:
// Rarity::Common -> "Common"
// Rarity::Rare -> "Rare"
// Rarity::Epic -> "Epic"
// anything else -> "Unknown"
//
// Use a SWITCH statement (practicing switch on a scoped enum is the point):
// switch (rarity)
// {
// case Rarity::Common: return "Common";
// ...
// default: return "Unknown";
// }
//
// DO NOT convert to int. Scoped enums switch cleanly (notes 13.6).
//
// >>> YOUR CODE HERE <<<
//
std::string_view rarityLabel(Rarity /*rarity*/)
{
return "Unknown"; // stub — replace with the switch above
}
// ─────────────────────────────────────────────────────────────────────────────
// ─── TASK 2: rarityMultiplier — switch returning an int ──────────────────────
//
// Return the integer value multiplier for `rarity`:
// Rarity::Common -> 1
// Rarity::Rare -> 3
// Rarity::Epic -> 10
// default -> 1 (safe fallback for any future enumerator)
//
// Use a SWITCH on the scoped enum, matching the style in Task 1.
//
// >>> YOUR CODE HERE <<<
//
int rarityMultiplier(Rarity /*rarity*/)
{
return 1; // stub — correct only for Common; Rare and Epic will be wrong
}
// ─────────────────────────────────────────────────────────────────────────────
// ─── TASK 3: itemWorth — pass by const ref, multiply three values ─────────────
//
// Total coin value of all stock of this item:
// item.quantity × item.unitValue × rarityMultiplier(item.rarity)
//
// IMPORTANT: the parameter is `const Item& item` — a CONST REFERENCE.
// • `const` means you cannot modify the struct (read-only).
// • `&` (reference) means no copy is made — especially important for the
// std::string member inside Item (notes 13.10, 13.12).
//
// Access members with the DOT operator: item.quantity, item.rarity, etc.
// Reuse rarityMultiplier(...) — do not inline the multiplier values here.
//
// >>> YOUR CODE HERE <<<
//
int itemWorth(const Item& /*item*/)
{
return 0; // stub — always 0 (correct only for quantity-0 edge case)
}
// ─────────────────────────────────────────────────────────────────────────────
// ─── TASK 4: makeItem — return a struct by value ──────────────────────────────
//
// Construct and RETURN an Item with the four supplied fields.
// Use AGGREGATE INITIALIZATION with braces (notes 13.8):
// return Item{ std::string{name}, rarity, quantity, unitValue };
//
// Notes:
// • `name` is a std::string_view; the Item's std::string member needs a copy.
// Write std::string{name} to construct the owning string from the view.
// The std::string-from-string_view constructor is EXPLICIT, so a bare
// `name` will NOT implicitly convert — you must spell out std::string{name}.
// • Return by VALUE (notes 13.10): the compiler performs copy elision (NRVO),
// so there is no copy overhead in practice.
//
// >>> YOUR CODE HERE <<<
//
Item makeItem(std::string_view /*name*/, Rarity /*rarity*/,
int /*quantity*/, int /*unitValue*/)
{
return Item{}; // stub — returns a default-constructed empty Item
}
// ─────────────────────────────────────────────────────────────────────────────
// ─── TASK 5: restock — mutate through a non-const reference ──────────────────
//
// Add `amount` units to item.quantity. If `amount` is negative, treat it as 0
// (do NOT decrease the stock count below what was there).
//
// PASS BY NON-CONST REFERENCE `Item& item` — that is what lets us modify the
// caller's object. The & is essential: without it, we'd modify a local copy and
// the caller would never see the change (notes 13.10, 13.12).
//
// Pattern:
// if (amount < 0) amount = 0;
// item.quantity += amount;
//
// >>> YOUR CODE HERE <<<
//
void restock(Item& /*item*/, int /*amount*/)
{
// stub — does nothing; the quantity will never change in tests
}
// ─────────────────────────────────────────────────────────────────────────────
// ─── TASK 6: isSameItem — compare two items by name and rarity ───────────────
//
// Return true if a and b have the same name AND the same rarity.
// The quantity and unitValue do NOT matter for catalogue identity.
//
// Both parameters are `const Item&` — read-only references; no copies needed.
// Access members with `.`: a.name, a.rarity, b.name, b.rarity
// std::string supports == for lexicographic comparison.
// enum class values support == directly (no cast needed, notes 13.6).
//
// >>> YOUR CODE HERE <<<
//
bool isSameItem(const Item& /*a*/, const Item& /*b*/)
{
return false; // stub — always says "different" (wrong for matching items)
}
// ─────────────────────────────────────────────────────────────────────────────
Try the lab first — the learning is in the attempt.
// Chapter 13 — Compound Types: Enums & Structs · Inventory Ledger (REFERENCE SOLUTION)
// ─────────────────────────────────────────────────────────────────────────────
// Complete, correct, warning-clean implementation of ../ledger.h.
// Peek only after you have made a genuine attempt in starter/ledger.cpp — the
// learning is in wrestling with the struct/enum API yourself, then comparing.
//
// Every function here demonstrates a different Chapter-13 pattern; the comments
// name the concept and note the section where it is taught.
#include "../ledger.h"
// ─── TASK 1: rarityLabel — switch on a scoped enum (notes 13.4, 13.6) ────────
//
// KEY TERM: SCOPED ENUM — the enumerators live inside Rarity's scope, so every
// case label is fully qualified: Rarity::Common, Rarity::Rare, Rarity::Epic.
// There is no implicit conversion to int; the switch just works on the enum
// type directly, which is cleaner and type-safe.
//
// The `default` case catches any enumerator value that is not explicitly handled
// — important for future-proofing and for -Wswitch (see notes 13.6 best-practice).
//
// Return type: std::string_view because the labels are string LITERALS with static
// storage duration — they outlive every call, so returning a non-owning view is
// safe and cheap (no allocation, no copy). (notes 13.4)
//
std::string_view rarityLabel(Rarity rarity)
{
switch (rarity)
{
case Rarity::Common: return "Common";
case Rarity::Rare: return "Rare";
case Rarity::Epic: return "Epic";
default: return "Unknown"; // future enumerators land here safely
}
}
// ─── TASK 2: rarityMultiplier — switch returning an int (notes 13.6) ─────────
//
// Same switch pattern as rarityLabel, but the result is an integer.
// We keep the multiplier in ONE place — if a game designer changes Epic from 10x
// to 15x, they edit only this function. itemWorth (below) and any future code
// that calls rarityMultiplier get the new value automatically.
//
// CS6340 tie-in: LLVM passes use exactly this pattern — a helper that maps an
// enum (e.g. `Instruction::Opcode`) to a property (cost, latency, string name)
// so the mapping stays in one spot and callers stay readable.
//
int rarityMultiplier(Rarity rarity)
{
switch (rarity)
{
case Rarity::Common: return 1;
case Rarity::Rare: return 3;
case Rarity::Epic: return 10;
default: return 1; // safe fallback: no additional multiplier
}
}
// ─── TASK 3: itemWorth — const reference parameter + member access (notes 13.10)
//
// KEY TERM: CONST REFERENCE — `const Item&` is the standard way to pass a
// struct for READ-ONLY access without copying it.
// • Without the `&`: the entire Item (including its std::string member) would
// be copied on every call — expensive and pointless.
// • Without `const`: the function could accidentally modify the caller's data.
// • With `const Item&`: zero-copy, read-only. This is the canonical pattern
// for read-only struct parameters (notes 13.10).
//
// Member access uses `.` — the item is a reference, not a pointer, so `.` is
// correct (not `->`, which is for pointers — see notes 13.12).
//
int itemWorth(const Item& item)
{
return item.quantity * item.unitValue * rarityMultiplier(item.rarity);
// ^^^^^^^^^ ^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// member member delegate to the multiplier helper —
// access access do NOT inline 1/3/10 here!
}
// ─── TASK 4: makeItem — returning a struct by value (notes 13.8, 13.10) ───────
//
// AGGREGATE INITIALIZATION: brace-initialize an Item using member values in
// DECLARATION ORDER (name, rarity, quantity, unitValue) — exactly the order
// they appear in the struct definition in ledger.h (notes 13.8).
//
// WHY RETURN BY VALUE: structs are just data. The compiler performs NRVO (Named
// Return Value Optimization) or copy elision in C++17, so this is typically
// zero-copy at the machine level. Returning structs avoids messy out-parameters
// and clearly communicates "this function produces an Item" (notes 13.10).
//
// std::string_view -> std::string: The `name` parameter is a non-owning view.
// Constructing `std::string{name}` materializes an owning copy for the struct's
// member. (The struct must OWN its string so it doesn't dangle — notes 13.11.)
//
Item makeItem(std::string_view name, Rarity rarity, int quantity, int unitValue)
{
return Item{ std::string{name}, rarity, quantity, unitValue };
// ^^^^^^^^^^^^^^^^ ^^^^^^ ^^^^^^^^ ^^^^^^^^^
// aggregate init: fills members in declaration order
}
// ─── TASK 5: restock — non-const reference (mutation through a ref) ─────────
//
// KEY TERM: NON-CONST REFERENCE — `Item& item` (no `const`) lets us modify the
// caller's struct in place. This is the canonical idiom for "in/out parameters"
// and for functions that logically mutate an object (notes 13.10, 13.12).
//
// Without the `&`, we would modify a LOCAL copy and the caller would never see
// the change — the classic Ch 12 mistake. The `&` is the whole point here.
//
// Clamping negative amounts to 0 prevents accidentally *reducing* stock via the
// restock path (a different function would handle selling/consuming).
//
void restock(Item& item, int amount)
{
if (amount < 0)
amount = 0; // treat negative restock as no-op (clamp to 0)
item.quantity += amount; // mutate through the reference — caller sees this
}
// ─── TASK 6: isSameItem — comparing struct fields (notes 13.7, 13.10) ────────
//
// BOTH parameters are `const Item&` — read-only references; no copies needed.
// We compare the CATALOGUE IDENTITY fields: name and rarity.
// • `std::string` supports `==` for lexicographic comparison.
// • `enum class` supports `==` directly — no cast needed (notes 13.6).
// Compare with `a.rarity == b.rarity` (not static_cast<int>).
//
// Why NOT compare quantity and unitValue? Two stockrooms can carry different
// amounts of the same item. Identity is about WHAT the item is, not HOW MANY.
//
bool isSameItem(const Item& a, const Item& b)
{
return a.name == b.name && a.rarity == b.rarity;
// ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^
// string comparison enum comparison — works directly on scoped enums
}
// Chapter 13 — Compound Types: Enums & Structs · Inventory Ledger (GRADER)
// ─────────────────────────────────────────────────────────────────────────────
// A tiny no-framework unit-test harness (same style as drills/CLAUDE.md spec).
// It includes ../ledger.h and calls each library function across many inputs.
// CHECK fails loudly with the expression and line number. Any failure ->
// non-zero exit -> `make test` is RED.
//
// The Makefile links this file against starter/ledger.cpp (your code) for
// `make test`, and against solution/ledger.cpp for `make test-solution`.
//
// Testing strategy:
// • rarityLabel / rarityMultiplier: round-trip enum values; test default arm.
// • itemWorth: verify formula across all three rarities; test zero-quantity edge.
// • makeItem: struct returned by value; check every field.
// • restock: mutation through reference; clamp negative amounts.
// • isSameItem: same/different name; same/different rarity; field independence.
#include <iostream>
#include "../ledger.h"
static int fails = 0;
// CHECK: assert a boolean condition; on failure print expression + line number.
#define CHECK(cond) \
do { if(!(cond)){ std::cerr << "FAIL: " #cond " @line " << __LINE__ << "\n"; ++fails; } } while(0)
int main()
{
// ── Task 1: rarityLabel — switch on a scoped enum ────────────────────────
// Normal round-trip: every named enumerator must map to its label string.
CHECK(rarityLabel(Rarity::Common) == "Common");
CHECK(rarityLabel(Rarity::Rare) == "Rare");
CHECK(rarityLabel(Rarity::Epic) == "Epic");
// Edge: a cast-forged value that matches no named enumerator must hit default.
CHECK(rarityLabel(static_cast<Rarity>(99)) == "Unknown"); // default arm
// ── Task 2: rarityMultiplier — switch returning an int ───────────────────
CHECK(rarityMultiplier(Rarity::Common) == 1);
CHECK(rarityMultiplier(Rarity::Rare) == 3);
CHECK(rarityMultiplier(Rarity::Epic) == 10);
// Edge: out-of-range cast value must return the safe default of 1.
CHECK(rarityMultiplier(static_cast<Rarity>(99)) == 1);
// ── Task 3: itemWorth — formula + const-ref parameter ───────────────────
// Common: 5 × 10 × 1 = 50
Item potion { "Potion", Rarity::Common, 5, 10 };
CHECK(itemWorth(potion) == 50);
// Rare: 2 × 40 × 3 = 240
Item sword { "Sword", Rarity::Rare, 2, 40 };
CHECK(itemWorth(sword) == 240);
// Epic: 1 × 100 × 10 = 1000
Item artifact { "Artifact", Rarity::Epic, 1, 100 };
CHECK(itemWorth(artifact) == 1000);
// Edge: zero-quantity item is worth 0 regardless of rarity or unitValue.
Item empty { "Ghost", Rarity::Epic, 0, 999 };
CHECK(itemWorth(empty) == 0); // 0 × 999 × 10 == 0
// Edge: zero unitValue item is worth 0 regardless of quantity or rarity.
Item junk { "Pebble", Rarity::Rare, 100, 0 };
CHECK(itemWorth(junk) == 0); // 100 × 0 × 3 == 0
// Verify itemWorth does NOT modify the struct (const ref guarantee).
CHECK(potion.quantity == 5); // must still be 5 after the call above
CHECK(potion.unitValue == 10);
// ── Task 4: makeItem — return struct by value ────────────────────────────
Item ring { makeItem("Ring", Rarity::Rare, 3, 25) };
CHECK(ring.name == "Ring");
CHECK(ring.rarity == Rarity::Rare);
CHECK(ring.quantity == 3);
CHECK(ring.unitValue == 25);
// Edge: makeItem with zero quantity and Common rarity.
Item coin { makeItem("Coin", Rarity::Common, 0, 1) };
CHECK(coin.name == "Coin");
CHECK(coin.rarity == Rarity::Common);
CHECK(coin.quantity == 0);
CHECK(coin.unitValue == 1);
// makeItem round-trip through itemWorth: 3 × 25 × 3 = 225
CHECK(itemWorth(ring) == 225);
// ── Task 5: restock — mutate through a non-const reference ──────────────
Item chest { "Chest", Rarity::Common, 4, 20 };
restock(chest, 6);
CHECK(chest.quantity == 10); // 4 + 6 = 10
restock(chest, 0);
CHECK(chest.quantity == 10); // adding 0 changes nothing
restock(chest, -5);
CHECK(chest.quantity == 10); // Edge: negative amount → clamped to 0; no change
restock(chest, 1);
CHECK(chest.quantity == 11); // normal positive restock after clamp
// Confirm restock does NOT affect other members.
CHECK(chest.name == "Chest");
CHECK(chest.rarity == Rarity::Common);
CHECK(chest.unitValue == 20);
// ── Task 6: isSameItem — catalogue identity by name + rarity ────────────
Item a { makeItem("Shield", Rarity::Rare, 1, 50) };
Item b { makeItem("Shield", Rarity::Rare, 9, 99) }; // different qty/value
Item c { makeItem("Shield", Rarity::Common, 1, 50) }; // same name, diff rarity
Item d { makeItem("Armor", Rarity::Rare, 1, 50) }; // same rarity, diff name
// Same name AND same rarity → same catalogue entry (regardless of stock).
CHECK(isSameItem(a, b) == true);
// Same name but DIFFERENT rarity → different items.
CHECK(isSameItem(a, c) == false);
// Different name but same rarity → different items.
CHECK(isSameItem(a, d) == false);
// Reflexive: an item is always the same as itself.
CHECK(isSameItem(a, a) == true);
// Edge: two default-constructed (empty) Items share the same identity.
Item x {};
Item y {};
CHECK(isSameItem(x, y) == true); // both "", Rarity::Common
// ── Result ───────────────────────────────────────────────────────────────
if (!fails)
std::cout << "PASS \xE2\x9C\x85 all ledger checks passed.\n";
else
std::cerr << "\nFAIL \xE2\x9D\x8C " << fails << " check(s) failed"
<< " — fix the TASK blocks in starter/ledger.cpp.\n";
return fails ? 1 : 0;
}
// ============================================================================
// ledger.h — PUBLIC INTERFACE of the Inventory Ledger library (Chapter 13)
// ----------------------------------------------------------------------------
// This header is COMPLETE and PROVIDED. Do not edit it.
//
// This header exercises the two central concepts of Chapter 13:
//
// 1) SCOPED ENUMERATION (enum class, notes 13.6)
// Rarity is an `enum class` — a PROGRAM-DEFINED TYPE whose values are a
// small, closed set of named alternatives. The `class` keyword means:
// • enumerators live INSIDE the type's scope (Rarity::Common, not Common)
// • NO implicit conversion to int — you must use static_cast if you ever
// need the underlying integer (notes 13.6 covers static_cast<int>(...))
// • No name-collision risk with unscoped enums (notes 13.2, 13.6)
//
// 2) STRUCT AGGREGATE (struct + aggregate initialization, notes 13.7–13.10)
// Item is a DATA-ONLY struct — it has named MEMBERS and is initialized
// with braces: Item item { "Sword", Rarity::Rare, 5, 40 };
// Important boundaries:
// • structs stay DATA-ONLY here; member FUNCTIONS are Chapter 14
// • the FREE FUNCTIONS below operate ON Items (extern to the struct)
// • aggregate init fills members in DECLARATION ORDER
//
// Header guard (notes 13.1 multi-file / Chapter 2): prevents double-inclusion.
// ============================================================================
#ifndef LEDGER_H
#define LEDGER_H
#include <string> // std::string — owning string member (Chapter 5)
#include <string_view> // std::string_view — non-owning view for labels (Chapter 5)
// ─── enum class Rarity ───────────────────────────────────────────────────────
//
// SCOPED ENUM (notes 13.6): enumerators are QUALIFIED by the type name.
// Access them as: Rarity::Common, Rarity::Rare, Rarity::Epic
//
// CS6340 tie-in: LLVM itself uses scoped enums extensively — for example,
// `Instruction::BinaryOps` and `AtomicOrdering` are `enum class` types that
// describe what an `Instruction` does. Same concept, bigger domain.
//
// FIXED UNDERLYING TYPE `: int` (notes 13.3 — "Underlying type"). This pins
// Rarity's storage to `int`. It does NOT re-enable implicit enum->int
// conversion (you still need static_cast<int> for the integer value), but it
// DOES make one thing well-defined that the grader relies on:
//
// static_cast<Rarity>(99)
//
// Without a fixed underlying type, an enum's representable range is only the
// smallest bit-field that holds its enumerators — here {0,1,2} fits in 2 bits,
// giving the range [0..3]. Casting 99 (outside that range) into the enum is
// UNDEFINED BEHAVIOR in C++17 (notes 13.3 warns parsing external integers can
// produce values "that do not match any named enumerator"). With `: int`, any
// value representable as int is a valid (if unnamed) Rarity, so the default arm
// of a switch is reachable *and* well-defined — which is exactly what the
// rarityLabel/rarityMultiplier "Unknown / safe fallback" tests exercise.
//
enum class Rarity : int
{
Common, // multiplier ×1 — everyday drops
Rare, // multiplier ×3 — uncommon finds
Epic, // multiplier ×10 — legendary gear
};
// ─── struct Item ─────────────────────────────────────────────────────────────
//
// AGGREGATE (notes 13.7–13.9): a DATA-ONLY container with four named members.
// Each member gets a DEFAULT INITIALIZER (notes 13.9) so a default-constructed
// Item is in a safe, known state:
//
// Item empty {}; // name="", rarity=Common, quantity=0, unitValue=0
// Item sword { "Sword", Rarity::Rare, 5, 40 }; // fully specified
//
// Member order is part of the aggregate API — callers rely on it.
//
struct Item
{
std::string name {}; // display name of the item (owned string)
Rarity rarity { Rarity::Common }; // how rare is it?
int quantity { 0 }; // units in stock
int unitValue{ 0 }; // base value in coins per unit
};
// ─── Free-function API (notes 13.10 — passing/returning structs) ──────────────
//
// These functions OPERATE ON Items but are NOT members of the struct.
// That boundary — data here, functions there — is the Chapter 13 lesson.
// Chapter 14 introduces member functions; resist the urge to move things there.
// ── rarityLabel ──────────────────────────────────────────────────────────────
// Returns a human-readable string for a Rarity value.
// Common → "Common", Rare → "Rare", Epic → "Epic", anything else → "Unknown"
// Implement with a SWITCH on the scoped enum (notes 13.4 / 13.6).
// Pass the enum BY VALUE (it's just an integer under the hood; no cost).
std::string_view rarityLabel(Rarity rarity);
// ── rarityMultiplier ─────────────────────────────────────────────────────────
// Returns the integer value multiplier for a Rarity:
// Common → 1, Rare → 3, Epic → 10, anything else → 1 (safe default).
int rarityMultiplier(Rarity rarity);
// ── itemWorth ────────────────────────────────────────────────────────────────
// Total coin value of the ENTIRE STOCK of this item:
// item.quantity × item.unitValue × rarityMultiplier(item.rarity)
//
// PASS BY CONST REFERENCE (notes 13.10): the struct is read-only and we avoid
// copying the std::string member. This is the canonical pattern for read-only
// struct parameters. Return by VALUE (a computed int — nothing to alias).
int itemWorth(const Item& item);
// ── makeItem ─────────────────────────────────────────────────────────────────
// Factory helper: constructs and RETURNS an Item by value (notes 13.10).
// makeItem("Potion", Rarity::Common, 10, 5)
// => Item{ "Potion", Rarity::Common, 10, 5 }
// Modern C++ performs copy elision/NRVO, so this is cheap.
// Returning a struct by value is cleaner than multiple out-parameters.
Item makeItem(std::string_view name, Rarity rarity, int quantity, int unitValue);
// ── restock ──────────────────────────────────────────────────────────────────
// Add `amount` units to item.quantity. Mutates the struct THROUGH a non-const
// reference (notes 13.10, notes 13.12): the caller's object is modified in place.
// If `amount` is negative it should be treated as 0 (clamp to zero before adding).
void restock(Item& item, int amount);
// ── isSameItem ───────────────────────────────────────────────────────────────
// Returns true if a and b represent the SAME catalogue entry: same name AND same
// rarity. Does NOT compare quantity/unitValue (those can differ across locations).
// Both passed by const reference — read-only, no copy (notes 13.10, 13.12).
bool isSameItem(const Item& a, const Item& b);
#endif // LEDGER_H
# Chapter 13 — Compound Types: Enums & Structs · Inventory Ledger · unit-test grader (Style B).
# Targets follow the drills/CLAUDE.md Makefile contract. TABS, not spaces.
#
# Layout:
# ledger.h — DECLARATIONS only (provided, complete; do NOT edit)
# starter/ledger.cpp — LEARNER fills in the 6 TASK blocks
# solution/ledger.cpp — reference implementation (peek if stuck)
# tests/tests.cpp — grader: includes ../ledger.h, calls the API, CHECKs results
#
# The Makefile switches which .cpp the tests link against:
# make test -> tests/tests.cpp + starter/ledger.cpp (RED until fixed)
# make test-solution -> tests/tests.cpp + solution/ledger.cpp (MUST be green)
CXX := clang++
CXXFLAGS := -std=c++17 -Wall -Wextra
.PHONY: all build run test solution test-solution clean
all: build
# ── build — compile-check the LEARNER's starter (warning-clean object) ────────
build:
$(CXX) $(CXXFLAGS) -c starter/ledger.cpp -o starter/ledger.o
@echo "OK \xE2\x9C\x85 starter/ledger.cpp compiles. Now run: make test"
# ── run — (no interactive driver for this lab; show a helpful message) ────────
run: build
@echo "This lab has no interactive driver. Run make test to grade your code,"
@echo "or make solution to build and run the reference solution demo."
# ── test — grade the LEARNER's code (RED until TASK blocks are filled in) ─────
test:
$(CXX) $(CXXFLAGS) tests/tests.cpp starter/ledger.cpp -o tests/run
@./tests/run || echo "FAIL \xE2\x9D\x8C fill in the TASK blocks in starter/ledger.cpp until every check passes."
# ── solution — build and run the REFERENCE solution ──────────────────────────
solution: solution/demo
./solution/demo
solution/demo: solution/ledger.cpp ledger.h
$(CXX) $(CXXFLAGS) tests/tests.cpp solution/ledger.cpp -o solution/demo
# ── test-solution — proof the exercise is solvable: reference MUST be green ───
test-solution:
$(CXX) $(CXXFLAGS) tests/tests.cpp solution/ledger.cpp -o tests/run
@./tests/run
# ── clean — remove all build artifacts ───────────────────────────────────────
clean:
rm -f starter/ledger.o tests/run solution/demo
rm -rf tests/run.dSYM solution/demo.dSYM
make test locally
(see “Build & run locally” above).