Chapter 26 · Templates and Classes
Exercise · Chapter 26

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 typesStack<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

  1. Write the Stack class template (in-class bodies). Replace the placeholder template <typename T, int Capacity = 8> class Stack with a complete implementation. Declare pop in-class; leave its body for Task 3. Constraints: use std::array<T, Capacity> and int m_size as private data; keep all bodies (except pop) inside the class; push returns false when full; top asserts non-empty.

  2. Write the TypeLabel primary template. Define template <typename T> struct TypeLabel with a static constexpr std::string_view name { "unknown" }. This is the catch-all that fires for any type you haven't specialized.

  3. Define Stack<T, Capacity>::pop out-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 decrement m_size. This is the only place in the exercise you write the full Stack<T, Capacity>:: qualification.

  4. Add three full specializations of TypeLabel. Using the template <> struct TypeLabel<XYZ> { … } syntax, make TypeLabel<int>::name == "int", TypeLabel<double>::name == "double", and TypeLabel<std::string_view>::name == "string_view".

Success criteria

  • Stack<int> push / top / pop lifecycle — size and top value after each op
  • push returning false when full (Stack<int, 3> filled to capacity)
  • Stack<std::string> — the second instantiation catches any int-only assumptions
  • Stack<std::string, 2> capacity enforcement
  • Stack<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
  • TypeLabel primary template: unrecognized type → "unknown"
  • TypeLabel<int>, TypeLabel<double>, TypeLabel<std::string_view> specializations
Concepts practiced
  • Class templatestemplate <typename T, int Capacity = 8> class Stack as a reusable stencil (notes 26.1)
  • Non-type template parametersCapacity as a compile-time integer that sizes std::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 syntaxtemplate <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/.cpp by default, and why this file is both the declaration and the definition (notes 26.1)
  • Full class template specializationtemplate <> 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& parameters, constexpr, std::string_view, bool return 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:

  • push returns bool (false-on-full) — no exceptions (Ch 27), no assert
  • top and pop use assert(!isEmpty()) for the precondition (Ch 9)
  • All member bodies except pop are defined in-class; pop is defined out-of-class using Stack<T, Capacity>::pop (the point of Task 3)
  • The full template definition — including the out-of-class pop — stays inside this header (no .cpp companion file)

Forbidden (not taught yet or out of scope):

  • new / delete / dynamic allocation (Ch 19 — use std::array, not T*)
  • std::vector as 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
shell
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 artifacts

make 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
C++
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
C++
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):

C++
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:

C++
void Counter::decrement() { --m_count; }  // no template head, no <T>

The two changes for a class template:

  1. Add template <typename T, int Capacity> before the return type.
  2. Change Stack::pop to Stack<T, Capacity>::pop.

That is the entire difference — everything else is normal member-function syntax.

Task 4 — full specialization syntax
C++
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 with template <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 via this.
  • "no member named name" for TypeLabel<int> — you have not added the template <> 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 but Stack<double, 16> uses 16 (already done in the design — but try adding Stack<int, 4> to your own test driver and see the type is different).
  • Add void clear() — reset m_size to 0 — practicing in-class definition style on a new member.
  • Add template <> struct TypeLabel<float> and TypeLabel<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 remove Capacity — observe how removing the non-type param simplifies the type signature but adds a dynamic-allocation cost.
starter/stack.h C++
// 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
Run
Submit
Run in your browser — coming soon For now: copy or download the files and use make test locally (see “Build & run locally” above).