Chapter 26 — Templates and Classes: `Stack<T>`
You're building a bounded stack as a class template, Stack<T, Capacity>.
The project forces you to confront the central constraint of class templates
head-on: because the compiler must stamp out a concrete Stack<int> or
Stack<std::string> at the point of use, the full template definition — data
layout, member functions, and all — must live in the header. There is no
.cpp split here; the header is the implementation. That constraint is not a
limitation to work around; it is the lesson.
The grader instantiates the stack with two element types — Stack<int> and
Stack<std::string> — so any accidental hardcoded-int assumption will surface
as a type error or a wrong result. A companion TypeLabel<T> trait demonstrates
full class specialization: the primary template says "unknown" for any
unrecognized type, while three explicit template <> specializations override
it for int, double, and std::string_view. Both together are small but
complete examples of the patterns LLVM uses for its container and type-system
helpers (SmallVector<T, N>, type traits, diagnostic utilities).
Your tasks
Write the
Stackclass template (in-class bodies). Replace the placeholdertemplate <typename T, int Capacity = 8> class Stackwith a complete implementation. Declarepopin-class; leave its body for Task 3. Constraints: usestd::array<T, Capacity>andint m_sizeas private data; keep all bodies (exceptpop) inside the class;pushreturnsfalsewhen full;topasserts non-empty.Write the
TypeLabelprimary template. Definetemplate <typename T> struct TypeLabelwith astatic constexpr std::string_view name { "unknown" }. This is the catch-all that fires for any type you haven't specialized.Define
Stack<T, Capacity>::popout-of-class. Write the out-of-class definition below the class body, using the pattern:template <typename T, int Capacity> void Stack<T, Capacity>::pop() { … }. The body should assert non-empty then decrementm_size. This is the only place in the exercise you write the fullStack<T, Capacity>::qualification.Add three full specializations of
TypeLabel. Using thetemplate <> struct TypeLabel<XYZ> { … }syntax, makeTypeLabel<int>::name == "int",TypeLabel<double>::name == "double", andTypeLabel<std::string_view>::name == "string_view".
Success criteria
Stack<int>push / top / pop lifecycle — size and top value after each oppushreturningfalsewhen full (Stack<int, 3>filled to capacity)Stack<std::string>— the second instantiation catches any int-only assumptionsStack<std::string, 2>capacity enforcementStack<int, 1>single-slot edge case- Push-pop-push reuse: a freed slot must accept a new push
- Default capacity (8): fill all eight slots, then pop in LIFO order
TypeLabelprimary template: unrecognized type →"unknown"TypeLabel<int>,TypeLabel<double>,TypeLabel<std::string_view>specializations
Concepts practiced
- Class templates —
template <typename T, int Capacity = 8> class Stackas a reusable stencil (notes 26.1) - Non-type template parameters —
Capacityas a compile-time integer that sizesstd::array<T, Capacity>(notes 26.2); the value is part of the type (Stack<int, 4>≠Stack<int, 8>) - In-class member function definitions — push, top, size, isEmpty, isFull defined inside the class body (notes 26.1)
- Out-of-class member definition syntax —
template <typename T, int Capacity> void Stack<T, Capacity>::pop()practiced in isolation (notes 26.1 / 26.2) - Header-only layout — why templates cannot split into
.h/.cppby default, and why this file is both the declaration and the definition (notes 26.1) - Full class template specialization —
template <> struct TypeLabel<int>overrides the primary template for one exact type (notes 26.4) - Precondition enforcement with assert — top() and pop() on an empty stack are undefined; assert guards them (reused from Ch 9)
std::array<T, N>as fixed backing storage (reused from Ch 17)- Reused:
const T¶meters,constexpr,std::string_view,boolreturn values, member-init{}zero-init (Ch 5, 10, 14)
Constraints
Allowed: std::array, assert, constexpr, std::string_view, in-class
member definitions, out-of-class member definitions with the
template <typename T, int Capacity> head, template <> full specializations,
default template arguments, static_cast<std::size_t> for indexing.
Required idioms:
pushreturnsbool(false-on-full) — no exceptions (Ch 27), no asserttopandpopuseassert(!isEmpty())for the precondition (Ch 9)- All member bodies except
popare defined in-class;popis defined out-of-class usingStack<T, Capacity>::pop(the point of Task 3) - The full template definition — including the out-of-class
pop— stays inside this header (no.cppcompanion file)
Forbidden (not taught yet or out of scope):
new/delete/ dynamic allocation (Ch 19 — usestd::array, notT*)std::vectoras the backing store (that uses dynamic allocation)- Partial specialization (notes 26.5 — present in the notes for reading comprehension, but not required here)
std::is_same,if constexpr, concepts / SFINAE (beyond Ch 26 scope)throw/try/catch(Ch 27)
Build & run locally
make # compile-check starter/stack.h (should already work)
make test # grade your stack.h -> RED until the TASK blocks are done
make test-solution # run the reference -> always GREEN
make solution # show the reference output
make clean # remove build artifactsmake test and make test-solution use -Istarter and -Isolution
respectively, so the same tests/tests.cpp file grades either copy (Style B2).
Hints
Task 1 — class template skeleton and non-type parameter
template <typename T, int Capacity = 8>
class Stack
{
std::array<T, Capacity> m_data {};
int m_size {};
public:
bool push(const T& value)
{
if (isFull()) return false;
m_data[static_cast<std::size_t>(m_size)] = value;
++m_size;
return true;
}
T top() const
{
assert(!isEmpty() && "Stack::top called on empty stack");
return m_data[static_cast<std::size_t>(m_size - 1)];
}
int size() const { return m_size; }
bool isEmpty() const { return m_size == 0; }
bool isFull() const { return m_size == Capacity; }
void pop(); // declaration only — body in Task 3
};Capacity is a non-type template parameter (notes 26.2): it is a
compile-time integer that becomes part of the type.
Stack<int, 4> and Stack<int, 8> are different types, just like
std::array<int, 4> and std::array<int, 8>.
Task 2 — primary template syntax
template <typename T>
struct TypeLabel
{
static constexpr std::string_view name { "unknown" };
};This is the "catch-all" (notes 26.4): it fires whenever no specialization
matches. constexpr makes name a compile-time constant — zero runtime cost.
Task 3 — out-of-class syntax (the tricky one)
The out-of-class definition must repeat the full template parameter list AND qualify the class name with both parameters (notes 26.1 / 26.2):
template <typename T, int Capacity>
void Stack<T, Capacity>::pop()
{
assert(!isEmpty() && "Stack::pop called on empty stack");
--m_size;
}Compare to a non-template out-of-class definition:
void Counter::decrement() { --m_count; } // no template head, no <T>The two changes for a class template:
- Add
template <typename T, int Capacity>before the return type. - Change
Stack::poptoStack<T, Capacity>::pop.
That is the entire difference — everything else is normal member-function syntax.
Task 4 — full specialization syntax
template <>
struct TypeLabel<int>
{
static constexpr std::string_view name { "int" };
};template <> means "all template parameters are now fixed — none remain"
(notes 26.4). The primary template must be defined first (Task 2).
Add one template <> block for each of int, double, and std::string_view.
The specialization is an independent class definition: it does not "inherit" from the primary template; it replaces it for that exact type.
Stuck on a compile error?
- "expected unqualified-id" near
Stack<int>— the placeholder class in the starter is not a template. Make sure your Task 1 replacement starts withtemplate <typename T, int Capacity = 8>. - "m_size was not declared" in the out-of-class pop — your Task 1 Stack must
have
int m_size {};as a private data member; pop accesses it viathis. - "no member named name" for
TypeLabel<int>— you have not added thetemplate <>specialization yet (Task 4), or you added it before the primary template was defined (reorder so Task 2 comes first, Task 4 second). - "-Wunused-parameter" warning — make sure you remove the
/*comment-out*/syntax from parameters once you use them (the stubs use/*value*/to avoid unused-parameter warnings; real implementations use the parameter directly).
Stretch goals
- Add a non-type template default for Capacity so
Stack<double>uses 8 slots butStack<double, 16>uses 16 (already done in the design — but try addingStack<int, 4>to your own test driver and see the type is different). - Add
void clear()— resetm_sizeto 0 — practicing in-class definition style on a new member. - Add
template <> struct TypeLabel<float>andTypeLabel<bool>as additional full specializations (notes 26.4 — same syntax as Task 4). - Read notes 26.5 and think about how you would write a partial specialization
Stack<T*, N>that stores raw pointers — then read the ownership warning in notes 26.6 about why that is more dangerous than it looks. - Flip the backing store to
std::vector<T>(Ch 16) and removeCapacity— observe how removing the non-type param simplifies the type signature but adds a dynamic-allocation cost.
// Chapter 26 — Templates and Classes · Project: Stack<T> (STARTER)
// ─────────────────────────────────────────────────────────────────────────────
// Fill in the four TASK blocks below. They map 1:1 to the tasks in the README
// and to the CHECKs in tests/tests.cpp. Every body currently compiles but
// returns PLACEHOLDER values so `make test` is RED. Your job: turn it GREEN.
//
// make build compile-check this header (should already work)
// make test grade your code (RED until the TASKs are filled in)
// make solution see the reference if you're truly stuck
//
// ── WHY HEADER-ONLY? ─────────────────────────────────────────────────────────
// This is the CENTRAL lesson of Chapter 26 (notes 26.1). When a translation
// unit writes Stack<int> the compiler must see the FULL class template
// definition RIGHT THERE so it can stamp out a concrete Stack<int>. If the
// member bodies lived in a .cpp file the linker would say "undefined symbol
// Stack<int>::push" — it never got the chance to instantiate them.
//
// Practical rule (notes 26.1):
// "For class templates, put the class definition and member definitions in the
// header, or put member definitions in an included .inl file at the bottom."
//
// This file is that header. Everything — data layout, in-class members, and
// the one out-of-class member (Task 3) — lives here.
// ─────────────────────────────────────────────────────────────────────────────
#ifndef STACK_H
#define STACK_H
#include <array> // std::array — Ch 17 fixed-size array
#include <cassert> // assert — Ch 9 precondition enforcement
#include <cstddef> // std::size_t
#include <string_view> // std::string_view — for TypeLabel (Ch 5)
// ─────────────────────────────────────────────────────────────────────────────
// TASK 1: Write the Stack class template
// ─────────────────────────────────────────────────────────────────────────────
// Design
// ──────
// Stack is a **class template** (notes 26.1) and uses a **non-type template
// parameter** for the capacity (notes 26.2). Its backing storage is a fixed-
// size std::array<T, Capacity> (Ch 17) — zero dynamic allocation, just like
// llvm::SmallVector's inline buffer.
//
// Signature to implement (replace the placeholder class below):
//
// template <typename T, int Capacity = 8>
// class Stack { ... };
//
// Required PUBLIC member functions (all defined IN-CLASS except pop — see
// Task 3 for the out-of-class syntax practice):
//
// bool push(const T& value)
// Add `value` on top. Return false if already full, true on success.
//
// T top() const
// Return the top element WITHOUT removing it.
// Precondition: stack is not empty — enforce with assert (Ch 9).
//
// int size() const
// Return the number of elements currently on the stack.
//
// bool isEmpty() const
// Return true when size() == 0.
//
// bool isFull() const
// Return true when size() == Capacity.
//
// void pop() <- DECLARE here; DEFINE in Task 3 (below class)
// Remove the top element.
// Precondition: stack is not empty — enforce with assert.
//
// Private data:
// std::array<T, Capacity> m_data {}; // the fixed-size backing store
// int m_size {}; // how many elements are live
//
// Key term: the TEMPLATE PARAMETER LIST template <typename T, int Capacity = 8>
// T — a TYPE parameter (placeholder for int, std::string, …)
// Capacity — a NON-TYPE parameter (a compile-time int value, default 8)
//
// Constraint: all member function bodies except pop go IN-CLASS (inside the
// class body { }). Only pop is declared in-class and DEFINED below (Task 3).
//
// Hint: inside the class body you may refer to the class simply as Stack;
// outside (Task 3) you must write Stack<T, Capacity>.
//
// ─── TASK 1 ──────────────────────────────────────────────────────────────────
//
// >>> YOUR CODE HERE <<<
//
// ─── placeholder — correct template signature, wrong bodies ──────────────────
// The class is already a template (so Stack<int> and Stack<std::string> parse)
// but every body returns a WRONG placeholder — that is why make test is RED.
// Replace this entire template with a correct implementation.
template <typename T, int Capacity = 8>
class Stack
{
public:
bool push(const T& /*value*/) { return false; } // placeholder: never pushes
T top() const { return T{}; } // placeholder: default-constructed
int size() const { return 0; } // placeholder: always 0
bool isEmpty() const { return true; } // placeholder: always empty
bool isFull() const { return true; } // placeholder: always full
void pop() {} // placeholder: does nothing
};
// ─────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// TASK 2: Add the TypeLabel primary template
// ─────────────────────────────────────────────────────────────────────────────
// A TRAIT-style template tells us the human-readable name of a type at compile
// time — exactly the kind of helper found inside LLVM's type system utilities.
//
// Write this primary template:
//
// template <typename T>
// struct TypeLabel
// {
// static constexpr std::string_view name { "unknown" };
// };
//
// "Primary template" (notes 26.4): the catch-all definition the compiler uses
// when no specialization matches. Any type with no explicit specialization will
// have TypeLabel<T>::name == "unknown".
//
// ─── TASK 2 ──────────────────────────────────────────────────────────────────
//
// >>> YOUR CODE HERE <<<
//
// ─── placeholder — wrong name for every type ─────────────────────────────────
template <typename T>
struct TypeLabel
{
// placeholder: all types get "unknown" (correct for unspecialized types, but
// the specialized ones like int, double, string_view also need a name — see Task 4).
static constexpr std::string_view name { "unknown" };
};
// ─────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// TASK 3: Define Stack<T, Capacity>::pop OUT-OF-CLASS
// ─────────────────────────────────────────────────────────────────────────────
// Pop is DECLARED inside the Stack class (Task 1) but DEFINED here, below the
// class body. This practices the out-of-class member syntax (notes 26.1):
//
// template <typename T, int Capacity>
// void Stack<T, Capacity>::pop()
// {
// assert(!isEmpty()); // precondition (Ch 9)
// --m_size; // "removing" the top is just shrinking the live count
// }
//
// Two required pieces (notes 26.1 / 26.2):
// template <typename T, int Capacity> <- repeat the FULL template parameter list
// Stack<T, Capacity>::pop <- qualify with the instantiation pattern
//
// Do NOT write just Stack::pop — outside the class body the compiler needs
// the full Stack<T, Capacity> qualification to find the right template.
//
// NOTE: Once you implement the real Stack in Task 1 (with m_size / m_data),
// replace the placeholder pop body below with the real one too.
// Currently it is left as an empty body so the file compiles with the stub class.
//
// ─── TASK 3 ──────────────────────────────────────────────────────────────────
//
// >>> YOUR CODE HERE <<<
//
// ─── placeholder out-of-class definition (empty body — wrong behavior) ────────
// (Once you add m_size in Task 1, replace this with the real implementation.)
// ─────────────────────────────────────────────────────────────────────────────
// ─────────────────────────────────────────────────────────────────────────────
// TASK 4: Full specializations of TypeLabel
// ─────────────────────────────────────────────────────────────────────────────
// Add FULL SPECIALIZATIONS (notes 26.4) so common types report their proper
// names instead of "unknown". The syntax:
//
// template <> // no remaining template parameters
// struct TypeLabel<int> // specialization for T = int exactly
// {
// static constexpr std::string_view name { "int" };
// };
//
// Required specializations (one for each):
// TypeLabel<int> -> name == "int"
// TypeLabel<double> -> name == "double"
// TypeLabel<std::string_view> -> name == "string_view"
//
// Key terms (notes 26.4):
// - "Full specialization" — ALL template parameters are fixed (contrast with
// partial specialization, which the notes cover in 26.5).
// - The PRIMARY template (Task 2) must be defined BEFORE any specialization.
//
// Constraint: do NOT change the primary template. Add three separate
// template <> struct TypeLabel<XYZ> { ... }; blocks below.
//
// ─── TASK 4 ──────────────────────────────────────────────────────────────────
//
// >>> YOUR CODE HERE <<<
//
// ─────────────────────────────────────────────────────────────────────────────
#endif // STACK_H
Try the lab first — the learning is in the attempt.
// Chapter 26 — Templates and Classes · Project: Stack<T> (REFERENCE SOLUTION)
// ─────────────────────────────────────────────────────────────────────────────
// Complete, correct, warning-clean reference for chapter-26/starter/stack.h.
// Peek only after taking a real swing at the starter — the learning is in
// wrestling with the template syntax yourself, then comparing here.
//
// ── WHY HEADER-ONLY? (notes 26.1) ────────────────────────────────────────────
// When a translation unit writes Stack<int> the compiler must see the FULL
// template definition at that point so it can stamp out a concrete Stack<int>.
// If the member bodies lived in a separate .cpp, that .cpp would compile but
// never produce a Stack<int> instantiation — the linker would then fail with
// "undefined symbol Stack<int>::push" because no translation unit ever asked
// for it while the definition was visible.
//
// Both in-class and the out-of-class pop definition therefore live here in the
// header. That makes this file a worked example of the constraint the chapter
// exists to teach.
// ─────────────────────────────────────────────────────────────────────────────
#ifndef STACK_H
#define STACK_H
#include <array> // std::array — Ch 17 fixed-size array
#include <cassert> // assert — Ch 9 precondition enforcement
#include <cstddef> // std::size_t
#include <string_view> // std::string_view — for TypeLabel labels (Ch 5)
// ─── TASK 1 + TASK 3: Stack class template (declaration + out-of-class pop) ──
//
// KEY TERM: CLASS TEMPLATE (notes 26.1)
// template <typename T, int Capacity = 8>
// class Stack { … };
//
// T is a TYPE parameter — the kind of value the stack holds.
// Capacity is a NON-TYPE parameter (notes 26.2) — a compile-time integer
// that fixes the array size. Default is 8 (an arbitrary small
// bound — the same idea as the inline-capacity tuning value in
// llvm::SmallVector<T, N>, explained two lines down).
//
// Why a non-type param here? Because std::array<T, Capacity> needs Capacity
// as a compile-time constant. A runtime constructor argument would require
// dynamic allocation (Ch 19), defeating the "zero-allocation bounded stack"
// goal. This is exactly the trade-off llvm::SmallVector makes: the inline-
// buffer size is a non-type param so it can be an actual array on the stack.
template <typename T, int Capacity = 8>
class Stack
{
// ── private data ────────────────────────────────────────────────────────
// m_data: the fixed backing store. We never resize it; m_size tracks how
// many elements are "live" (i.e. pushed but not yet popped).
std::array<T, Capacity> m_data {}; // value-initialized (zeros / default ctors)
int m_size {}; // 0 … Capacity
public:
// ── push ────────────────────────────────────────────────────────────────
// KEY DESIGN CHOICE: push returns bool instead of throwing (Ch 27 style)
// or asserting. The caller decides what "full" means for its use case.
// Return false when the stack is already full so the caller can react
// (e.g., a fuzzer queue might flush and retry rather than crash).
bool push(const T& value)
{
if (isFull())
return false; // precondition not met — caller must handle
m_data[static_cast<std::size_t>(m_size)] = value;
++m_size;
return true;
}
// ── top ─────────────────────────────────────────────────────────────────
// Returns the top element WITHOUT removing it. Calling top() on an empty
// stack is undefined behaviour in std::stack, so we enforce the precondition
// with assert (Ch 9) — fast in debug, zero-cost in release.
T top() const
{
assert(!isEmpty() && "Stack::top called on empty stack");
return m_data[static_cast<std::size_t>(m_size - 1)];
}
// ── size / isEmpty / isFull ──────────────────────────────────────────────
// These are small enough that keeping them in-class is normal and clear.
int size() const { return m_size; }
bool isEmpty() const { return m_size == 0; }
bool isFull() const { return m_size == Capacity; }
// ── pop (declaration only — DEFINED BELOW the class) ────────────────────
// TASK 3 teaches the out-of-class member definition syntax. The body is
// deliberately defined below so you can see the template<typename T, int
// Capacity> Stack<T, Capacity>::pop() pattern in isolation, exactly as
// notes 26.1 and 26.2 describe.
void pop();
};
// ─── TASK 3: out-of-class member definition ──────────────────────────────────
//
// KEY SYNTAX (notes 26.1 / 26.2):
//
// template <typename T, int Capacity> ← REPEAT the full parameter list
// void Stack<T, Capacity>::pop() ← qualify with BOTH params
// { … }
//
// Inside the class body you can just write Stack; outside it the compiler needs
// the full Stack<T, Capacity> to disambiguate (there are infinitely many
// instantiations; the "which Stack" must be spelled out).
//
// Compare to a non-template:
// int Counter::value() const { … } ← just "Counter", no template head
//
// Adding "template <typename T, int Capacity>" and changing "Stack" to
// "Stack<T, Capacity>" is the only extra work class templates add to out-of-
// class definitions. Once you've written it once the pattern sticks.
//
template <typename T, int Capacity>
void Stack<T, Capacity>::pop()
{
assert(!isEmpty() && "Stack::pop called on empty stack");
--m_size; // the element at m_data[m_size] is now "above" the live region;
// it will be overwritten the next time push() lands there.
}
// ─── TASK 2: TypeLabel primary template ──────────────────────────────────────
//
// KEY TERM: TRAIT (notes 26.4 reading between lines)
// A "trait" is a class template whose sole purpose is to carry compile-time
// information ABOUT a type. TypeLabel is a minimal trait: given any type T,
// TypeLabel<T>::name is a human-readable string_view known at compile time.
//
// The canonical standard-library example of this exact pattern is
// std::numeric_limits<T> — a class template fully specialized per arithmetic
// type to expose compile-time facts about it (e.g. std::numeric_limits<int>::max()).
// std::iterator_traits<T> is another. LLVM uses the same shape in real trait
// templates such as llvm::format_provider<T> (specialized per type to teach the
// formatting library how to print that type). The common thread: a class
// template, specialized per type, that describes a type's properties at compile
// time without altering the type itself.
//
// The PRIMARY template is the catch-all: it fires when NO specialization matches.
// This one says "I don't know what this type is → call it 'unknown'."
//
template <typename T>
struct TypeLabel
{
static constexpr std::string_view name { "unknown" };
};
// ─── TASK 4: full specializations of TypeLabel ───────────────────────────────
//
// KEY TERM: FULL CLASS SPECIALIZATION (notes 26.4)
// template <>
// struct TypeLabel<int> { … };
//
// "template <>" means "all template parameters are now fixed" — no placeholders
// remain. The angle brackets after the class name carry the specific type(s).
//
// The PRIMARY template MUST be visible before any specialization, just as it is
// here (Task 2 above, Task 4 below).
//
// Why not just an if/switch at runtime? Because these names are constants used
// in assertions and error messages that the compiler can fold into the binary —
// zero runtime overhead, and they communicate intent clearly to readers.
//
// TypeLabel<int> ────────────────────────────────────────────────────────────
template <>
struct TypeLabel<int>
{
static constexpr std::string_view name { "int" };
};
// TypeLabel<double> ─────────────────────────────────────────────────────────
template <>
struct TypeLabel<double>
{
static constexpr std::string_view name { "double" };
};
// TypeLabel<std::string_view> ───────────────────────────────────────────────
template <>
struct TypeLabel<std::string_view>
{
static constexpr std::string_view name { "string_view" };
};
#endif // STACK_H
// Chapter 26 — Templates and Classes · Project: Stack<T> (GRADER)
// ─────────────────────────────────────────────────────────────────────────────
// No-framework unit-test harness (drills/CLAUDE.md Style B2).
// The Makefile compiles this file with -Istarter or -Isolution so the correct
// stack.h is found via the plain #include "stack.h" below.
//
// make test -> grades YOUR starter/stack.h (RED until done)
// make test-solution -> grades solution/stack.h (must be GREEN)
//
// The tests instantiate BOTH Stack<int> AND Stack<std::string> (the core
// lesson: one template, many element types) plus edge cases for push-when-full,
// top/pop-preconditions, and TypeLabel specializations.
// ─────────────────────────────────────────────────────────────────────────────
#include <iostream>
#include <string>
#include <string_view>
#include "stack.h" // resolved via -Istarter or -Isolution (Style B2)
static int fails = 0;
// CHECK — print what failed and where; increment the failure counter.
#define CHECK(cond) \
do { if (!(cond)) { \
std::cerr << "FAIL: " #cond " @line " << __LINE__ << "\n"; \
++fails; \
} } while (0)
int main()
{
// ── Task 1 + 3: Stack<int> — basic operations ─────────────────────────────
{
Stack<int> s;
// fresh stack is empty and not full
CHECK(s.isEmpty());
CHECK(!s.isFull());
CHECK(s.size() == 0);
// push three values, check size and top after each
CHECK(s.push(10));
CHECK(s.size() == 1);
CHECK(!s.isEmpty());
CHECK(s.top() == 10);
CHECK(s.push(20));
CHECK(s.size() == 2);
CHECK(s.top() == 20); // top is LAST pushed
CHECK(s.push(30));
CHECK(s.size() == 3);
CHECK(s.top() == 30);
// pop (out-of-class definition — Task 3)
s.pop();
CHECK(s.size() == 2);
CHECK(s.top() == 20); // popping 30 reveals 20
s.pop();
CHECK(s.size() == 1);
CHECK(s.top() == 10);
s.pop();
CHECK(s.isEmpty());
CHECK(s.size() == 0);
}
// ── Task 1: push-when-full returns false ──────────────────────────────────
{
Stack<int, 3> small; // non-type param: capacity 3 (notes 26.2)
CHECK(small.push(1));
CHECK(small.push(2));
CHECK(small.push(3));
CHECK(small.isFull());
CHECK(small.size() == 3);
bool ok = small.push(99); // must return false — stack is full
CHECK(!ok);
CHECK(small.size() == 3); // size unchanged
CHECK(small.top() == 3); // top unchanged
}
// ── Task 1: Stack<std::string> — the second instantiation ─────────────────
// The whole point: ONE class template, TWO element types. If your template
// accidentally hardcodes int assumptions, these checks will catch it.
{
Stack<std::string> words;
CHECK(words.isEmpty());
CHECK(words.push("hello"));
CHECK(words.push("world"));
CHECK(words.size() == 2);
CHECK(words.top() == "world");
words.pop(); // out-of-class pop with std::string
CHECK(words.size() == 1);
CHECK(words.top() == "hello");
words.pop();
CHECK(words.isEmpty());
}
// ── Task 1: Stack<std::string> fill to capacity ───────────────────────────
{
Stack<std::string, 2> tiny;
CHECK(tiny.push("alpha"));
CHECK(tiny.push("beta"));
CHECK(tiny.isFull());
CHECK(!tiny.push("gamma")); // full: must return false
CHECK(tiny.size() == 2);
CHECK(tiny.top() == "beta");
}
// ── Task 1 edge: single-element stack ────────────────────────────────────
{
Stack<int, 1> one;
CHECK(one.isEmpty());
CHECK(!one.isFull());
CHECK(one.push(42));
CHECK(one.isFull());
CHECK(!one.isEmpty());
CHECK(one.top() == 42);
CHECK(!one.push(99)); // full after one push
one.pop();
CHECK(one.isEmpty());
}
// ── Task 1 edge: push-pop-push reuse ─────────────────────────────────────
// After popping, the capacity slot should be reusable.
{
Stack<int, 2> s;
CHECK(s.push(1));
CHECK(s.push(2));
CHECK(s.isFull());
s.pop();
CHECK(!s.isFull());
CHECK(s.push(3)); // slot freed by pop is now reusable
CHECK(s.isFull());
CHECK(s.top() == 3);
CHECK(s.size() == 2);
}
// ── Task 2 + 4: TypeLabel primary template and specializations ────────────
// TypeLabel<T>::name is a compile-time string_view (constexpr).
// Unknown type: primary template must say "unknown".
{
// Use an unspecialized type as a stand-in for "unknown"
struct MyLocalType {};
CHECK(TypeLabel<MyLocalType>::name == "unknown");
// Full specializations (Task 4) must override the primary
CHECK(TypeLabel<int>::name == "int");
CHECK(TypeLabel<double>::name == "double");
CHECK(TypeLabel<std::string_view>::name == "string_view");
}
// ── Task 2 + 4: TypeLabel with Stack instantiation types ─────────────────
// A realistic use case: the label follows the element type through the
// template. This pattern appears in LLVM diagnostic helpers that print the
// type name of a container's element.
{
CHECK(TypeLabel<int>::name == "int"); // still holds after Stack use
CHECK(TypeLabel<double>::name == "double"); // specialization is consistent
}
// ── Task 3: out-of-class pop syntax — exhaustive round-trip ─────────────
// Push the default capacity (8), verify order, pop all, verify empty.
{
Stack<int> full; // default Capacity = 8
for (int i = 1; i <= 8; ++i)
CHECK(full.push(i));
CHECK(full.isFull());
CHECK(!full.push(99)); // one more push must fail
// pop in LIFO order: 8, 7, … 1
for (int expected = 8; expected >= 1; --expected)
{
CHECK(full.top() == expected);
full.pop();
}
CHECK(full.isEmpty());
}
// ── Final verdict ─────────────────────────────────────────────────────────
if (!fails)
std::cout << "PASS \xE2\x9C\x85 all stack checks passed.\n";
else
std::cerr << "\nFAIL \xE2\x9D\x8C " << fails
<< " check(s) failed — fix the TASK blocks in starter/stack.h.\n";
return fails ? 1 : 0;
}
# Chapter 26 — Templates and Classes · Stack<T> · header-only unit-test grader (Style B2).
# Targets follow the drills/CLAUDE.md Makefile contract. TABS, not spaces.
#
# Style B2 explanation (CLAUDE.md):
# The learner edits starter/stack.h (the FULL class template, bodies inline).
# tests/tests.cpp does #include "stack.h" and the Makefile injects the
# right directory via -I so the correct stack.h is found.
#
# test: ; $(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/run && ./tests/run
# test-solution: ; $(CXX) $(CXXFLAGS) -Isolution tests/tests.cpp -o tests/run && ./tests/run
#
# Why no starter/main.cpp? The test harness supplies its own main(). A stack
# template doesn't need a "playable driver" the way the engine did — the grader
# IS the demo. (See chapter-08 for the Style B variant with a separate demo.)
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/stack.h (warning-clean).
# Because everything is header-only, we compile tests.cpp against the starter
# for the build check so any syntax error in the header is caught immediately.
build:
@$(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/build_check 2>&1 || true
@$(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/build_check
@echo "OK \xE2\x9C\x85 starter/stack.h compiles. Now run: make test"
# run — just re-runs the build check (there is no interactive driver here).
run: build
@echo "(no interactive driver for this exercise — use 'make test' to grade)"
# test — grade the LEARNER's code: compile + run against starter/stack.h.
# RED until the TASK blocks are filled in; GREEN once they are.
test:
$(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/run
@./tests/run || echo "FAIL \xE2\x9D\x8C fill in the TASK blocks in starter/stack.h until every check passes."
# solution — show the reference solution output (acts as a demo).
solution:
$(CXX) $(CXXFLAGS) -Isolution tests/tests.cpp -o tests/run_sol
./tests/run_sol
# test-solution — proof the lab is solvable: solution MUST pass every check.
test-solution:
$(CXX) $(CXXFLAGS) -Isolution tests/tests.cpp -o tests/run
@./tests/run
clean:
rm -f tests/run tests/run_sol tests/build_check
rm -rf tests/run.dSYM tests/run_sol.dSYM tests/build_check.dSYM
make test locally
(see “Build & run locally” above).