Chapter 11 · Function Overloading and Function Templates
Exercise · Chapter 11

Chapter 11 — Function Overloading and Function Templates: The Generic Toolkit

You will build a small generic toolkit mini-library — a set of functions that could plausibly live in any real C++ utility header. The key design goal is making overload resolution and template instantiation observable: rather than just writing code that compiles, you write code whose runtime output proves which overload or instantiation the compiler selected.

The library has four overloaded describe() functions (one for int, one for double, one for bool, and one for std::string_view) that each return a short labeled string like "int:42" or "bool:true". A test that expects describe(true) == "bool:true" will fail with "int:1" if the bool overload is missing — because bool promotes to int (notes 11.3), and the test tells you exactly which wrong overload fired. You also implement four function templates (myMin<T>, myClamp<T>, repeatChar<N>, scaled<T,U>) and one function that demonstrates a default argument (formatCount).

Why this design? In CS6340 LLVM passes you will read APIs like:

C++
void instrument(Function& function);      // overloaded for Function
void instrument(BasicBlock& block);       // overloaded for BasicBlock
void instrument(Instruction& instr);      // overloaded for Instruction

and generic utilities like SmallVector<T>, ArrayRef<T>, and PointerIntPair<T,N>. The patterns you practice here — overloads, templates, non-type template params — are the same patterns those APIs use.

Your tasks

  1. describe(int) — the int overload. Return "int:<value>" for each expected input (0, 1, -1, 42, -99, 100) using a switch statement. Return "int:?" for any other value.

  2. describe(double) — the double overload. Return "double:<value>" for each expected input using an if / else if chain (doubles cannot be used as switch case constants). Return "double:?" for unknown values.

  3. describe(bool) — the bool overload. Return "bool:true" or "bool:false". This is the key overload-resolution task: without it, describe(true) calls describe(int) because bool promotes to int (notes 11.3). The test checks for "bool:true", not "int:1" — that difference is the lesson made visible. Use a ternary ?: or if/else.

  4. describe(std::string_view) — the string overload. Return "str:<value>" for "hello", "LLVM", and "" (empty). Use an if / else if chain. Note: the grader passes "hello"sv (the sv suffix makes the argument already a string_view — an exact match at step 1) to show why this matters.

  5. myMin<T> — a function template. Write a single template that returns the smaller of two same-type values. Use operator< and a ternary. Test it with both int and double. The grader also calls myMin<double>(3, 4.5) to exercise explicit template arguments — the int 3 is converted to 3.0 because you forced T = double (notes 11.7).

  6. myClamp<T> — another template with boundary logic. Clamp value into [lo, hi]: if value < lo return lo, if value > hi return hi, otherwise return value unchanged. An if / else if / else chain is the right shape. Works for int, double, and any T with <.

  7. repeatChar<N> — non-type template parameter. Return a string_view of N identical copies of ch. N is an int non-type template parameter — a compile-time constant that sizes a static char buf[N + 1] array. Each distinct N (e.g. repeatChar<3> vs repeatChar<5>) is a separate instantiation with its own static buffer (notes 11.9, 11.7).

  8. scaled<T,U> — two-type template with auto return. Multiply a (type T) by b (type U) and return the result. Use two type parameters so int and double arguments can be mixed without explicit casts. Use auto as the return type so the compiler deduces it from a * b — for int * double the result is double, preserving the fractional part (notes 11.8).

  9. formatCount — default argument. Return a count string: singular ("1 item") when count == 1, plural ("3 items") otherwise. The suffix parameter has a default value of "s". The default is already declared in the header — do not repeat it in the function body (notes 11.5). Use two static char buffers (64 bytes each) to keep the returned string_view valid.

Success criteria

  • describe(true) == "bool:true" and describe(true) != "int:1" — the bool overload must be the exact match, not the int promotion path (notes 11.3)
  • myMin(5, 3) == 3 — the template must compare, not just return a
  • myMin<double>(3, 4.5) ~= 2.7explicit template argument must force T=double so int is converted before comparison (notes 11.7)
  • myClamp(0, 1, 10) == 1 — below lo must clamp to lo, not pass through
  • myClamp(0, 3, 3) == 3 — lo == hi edge case must clamp to that single value
  • repeatChar<3>('*') == "***" — three chars, correct content, size == 3
  • scaled(3, 2.5) ~= 7.5auto return must be double, not truncated int
  • formatCount("item", 1) == "1 item" — singular, no suffix
  • formatCount("item", 3) == "3 items" — plural with default suffix "s"
  • formatCount("pass", 1, "es") == "1 pass" — singular ignores suffix
Concepts practiced
  • Overloaded functions and what differentiates them (notes 11.1, 11.2)
  • Overload resolution — exact match beats promotion beats conversion (notes 11.3)
  • bool overload preventing silent boolint promotion (notes 11.3)
  • = delete for forbidden conversions — shown in comments (notes 11.4)
  • Default arguments — right-aligned, in declarations (notes 11.5)
  • Function templates (template <typename T>) as type-parameterized code patterns (notes 11.6, 11.7)
  • Template argument deduction — the compiler infers T from arguments (11.7)
  • Explicit template argumentsmyMin<double>(3, 4.5) forces T=double (11.7)
  • Multiple template type parameterstemplate <typename T, typename U> for mixed-type operations (notes 11.8)
  • auto return type — deduced from the return expression (notes 11.8)
  • Non-type template parameterstemplate <int N> for compile-time constants (notes 11.9)
  • Per-instantiation static locals — each repeatChar<N> instantiation gets its own static buffer (notes 11.7)
  • Templates in headers — why the full definition must be visible to callers (notes 11.10)
  • Reused from earlier chapters: std::string_view (Ch 5), switch / if/else / for (Ch 4, 8), const correctness (Ch 5), inline (Ch 7), static_cast (Ch 10), header guards (Ch 2)
Constraints

Allowed (Chapter ≤ 11):

  • template <typename T> and template <int N> function templates
  • Overloaded functions differentiated by parameter type
  • = delete (shown in comments; tests cannot require a compile error)
  • Default arguments (right-aligned, in declarations)
  • switch, if/else, for, while, ?: — all control flow from Ch 4–8
  • std::string_view, const, auto, static_cast, inline, static
  • <string_view>, <cmath> (for std::abs in tests only)

Forbidden (not yet taught):

  • Class templates (Ch 26) — use only function templates
  • Abbreviated auto parameters auto add(auto a, auto b) (C++20 — notes 11.8 mentions this but CLAUDE.md scope forbids using it)
  • std::string, std::to_string, std::snprintf — stay on-scope; manual char copying is intentional here
  • std::vector, std::array, pointers (Ch 12+), struct/class (Ch 13+)
  • goto, unnamed namespaces inside function bodies, global mutable state

Required idioms:

  • Templates defined in the header (not a .cpp file) — notes 11.10
  • Default argument in the declaration, not the definition — notes 11.5
  • Each template function uses only the concepts it documents
Build & run locally
shell
make            # compile-check starter/toolkit.h AND build the test binary
make run        # same as make test (no separate driver for this lab)
make test       # grade your toolkit  ->  RED until TASK blocks are filled in
make solution   # build + run the reference solution
make clean      # remove build artifacts

make test links tests/tests.cpp against your starter/toolkit.h (via -Istarter). make test-solution uses solution/toolkit.h instead — same grader, different include path.

Hints
Task 1 — switch on int values
C++
inline std::string_view describe(int value)
{
    switch (value)
    {
    case  0:   return "int:0";
    case  1:   return "int:1";
    case -1:   return "int:-1";
    case  42:  return "int:42";
    case -99:  return "int:-99";
    case  100: return "int:100";
    default:   return "int:?";
    }
}

Each return exits the switch and the function — no break needed when you use return in each arm. default catches anything the grader doesn't expect.

Task 3 — the bool overload and why it matters

The ternary form is cleanest:

C++
inline std::string_view describe(bool value)
{
    return value ? "bool:true" : "bool:false";
}

To see what happens without this overload: comment it out and run make test. You'll see describe(true) returns "int:1" — the bool promoted to int (step 2 of resolution) and the int overload won. With the bool overload the compiler stops at step 1 (exact match) and never tries a promotion.

Task 4 — why use the sv suffix in tests

A plain "hello" has type const char*. Converting const char* to bool is a standard conversion (non-null pointer → true, step 3). Converting const char* to std::string_view is a user-defined conversion (step 4). Standard conversions beat user-defined conversions in overload resolution, so describe("hello") would call describe(bool) and return "bool:true" — wrong!

The "hello"sv suffix creates a std::string_view literal directly, which is an exact match (step 1) for describe(std::string_view). This is the resolution rule made physical: always check what TYPE an expression actually has.

Task 5 — myMin template and explicit T
C++
template <typename T>
T myMin(T a, T b)
{
    return (a < b) ? a : b;
}

For myMin<double>(3, 4.5): the explicit <double> tells the compiler T=double before it looks at the arguments. So int 3 is converted to double 3.0 as part of passing it to the double parameter. Without <double>, the compiler would see one argument suggesting T=int and another suggesting T=double and report an error — it cannot deduce a single T from two different types (notes 11.7).

Task 6 — myClamp with operator< only
C++
template <typename T>
T myClamp(T value, T lo, T hi)
{
    if (value < lo)
        return lo;
    else if (hi < value)
        return hi;
    else
        return value;
}

Note: hi < value rather than value > hi — both work for built-in types, but writing it consistently as < means the template works for any type that only defines operator< (a common convention in the standard library).

Task 7 — non-type parameter and static per-instantiation buffer
C++
template <int N>
std::string_view repeatChar(char ch)
{
    static char buf[N + 1];   // sized at compile time; ONE per <N> instantiation
    for (int i = 0; i < N; ++i)
        buf[i] = ch;
    buf[N] = '\0';
    return std::string_view{buf, N};
}

N is baked into the type at the call site: repeatChar<3>('*') and repeatChar<5>('-') are different functions with different static buffers. If you called repeatChar<3> twice with different chars the second call overwrites the buffer — but the test only checks the return value immediately, so that is fine here.

Task 8 — two-type template and auto return
C++
template <typename T, typename U>
auto scaled(T a, U b)
{
    return a * b;
}

auto deduces the return type from the expression a * b. For int * double the standard arithmetic conversion rules promote int to double and the result is double. If you wrote T instead of auto and called scaled<int, double>, the double result would be narrowed to int — losing the fractional part.

Task 9 — formatCount with static buffers

The singular branch is straightforward — copy "1 " + label chars into a static array. For the plural branch:

C++
static char plural[64];
int i = 0;
// Write count (works for 0–99)
if (count >= 10) plural[i++] = static_cast<char>('0' + (count / 10) % 10);
plural[i++] = static_cast<char>('0' + count % 10);
plural[i++] = ' ';
for (std::size_t k = 0; k < label.size() && i < 60; ++k)
    plural[i++] = label[k];
for (std::size_t k = 0; k < suffix.size() && i < 63; ++k)
    plural[i++] = suffix[k];
plural[i] = '\0';
return std::string_view{plural, static_cast<std::size_t>(i)};

The static buffer lives for the whole program, so returning a string_view into it is safe. (This is a preview of lifetime thinking — formally Chapter 12.)

Stretch goals
  • Add a describe(char) deleted overload (notes 11.4) and observe that describe('A') becomes a compile error instead of silently calling describe(int). (Just uncomment the line in starter/toolkit.h.)
  • Extend myMin to a myMax, then build myClamp by calling myMax(lo, myMin(value, hi)) — shows how templates compose just like overloads do.
  • Add a third type parameter to scaled and generalize to three-value multiply (compare with the three-template-type overload in notes 11.8).
  • Replace the manual integer-to-char conversion in formatCount with std::to_string (Ch 5) or std::snprintf (Ch 28) once those chapters land.
  • Add an isInsideClosedRange<T>(value, lo, hi) template (from notes 11.7) and test it with int, double, and char — showing that < has the expected meaning for all three.
starter/toolkit.h C++
// ============================================================================
//  toolkit.h  —  the generic toolkit mini-library  (Chapter 11 STARTER)
// ----------------------------------------------------------------------------
//  This is the file you edit. It combines DECLARATIONS and DEFINITIONS in one
//  header. That is unusual — normally you split declarations into .h and bodies
//  into .cpp (Ch 2). Templates are the exception: the compiler must see the
//  full template body at every call site to generate the needed function, so
//  templates LIVE in headers (notes 11.10).
//
//  The non-template functions (TASK 1–4, TASK 9) are also inline here so
//  that the same grader binary works for both starter and solution without
//  requiring a separate link step (Style B2 — same pattern as Ch 14).
//
//  THREE Chapter-11 ideas are on display:
//
//    1) OVERLOADED FUNCTIONS — four describe() functions share one name but
//       differ by parameter type.  The compiler picks the right one based only
//       on the argument type at the call site (notes 11.1, 11.2). Each overload
//       returns a LABELED string so you can see which one the compiler chose.
//
//    2) FUNCTION TEMPLATES — myMin<T>, myClamp<T>, and scaled<T,U> are written
//       once and work for any type that supports the required operators.  No
//       copy-pasting the algorithm for each type (notes 11.6, 11.7, 11.8).
//
//    3) NON-TYPE TEMPLATE PARAMETER — repeatChar<N>() bakes the repetition
//       count into the type itself, giving a compile-time constant (notes 11.9).
//
//  CS6340 / LLVM lens: LLVM uses all three patterns.  Overloads let one name
//  handle Function, BasicBlock, and Instruction; templates power SmallVector<T>
//  and ArrayRef<T>; non-type params appear in PointerIntPair<T,N>.
//
//  Header guard (Ch 2): stops this file being pasted in twice per build.
// ============================================================================

#ifndef TOOLKIT_H
#define TOOLKIT_H

#include <string_view>   // std::string_view — cheap read-only text view (Ch 5)

// ============================================================================
// PART A — OVERLOADED describe() FUNCTIONS  (TASK 1 – 4)
// ============================================================================
//
// Four overloads of describe() differentiated by parameter type (notes 11.2).
// Each returns a short labeled string_view, e.g. describe(42) -> "int:42".
// The label makes OVERLOAD RESOLUTION visible: if the wrong overload fires,
// the test fails with a mismatch you can read.
//
// All four bodies are inline in this header (the Style B2 pattern) — that lets
// the Makefile swap starter/ vs solution/ with just a change in -I flags.

// ─── TASK 1: describe(int) ───────────────────────────────────────────────────
// Return "int:<value>" for each expected input.
// The grader passes: 0, 1, -1, 42, -99, 100.
// Use a switch on `value`. Return "int:?" for any other value.
//
// Why a switch over pre-canned values rather than std::to_string?
//   Templates and overloads are the focus here, not string formatting (Ch 28).
//   Hard-coded labels keep the tests crisp and eliminate formatting surprises.
//
// ─── TASK 1: replace the placeholder body ───────────────────────────────────
inline std::string_view describe(int value)
{
    (void)value;       // suppress unused-parameter warning on the placeholder
    return "int:?";   // placeholder — always wrong; fill in a switch statement
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 2: describe(double) ────────────────────────────────────────────────
// Return "double:<value>" for each expected input.
// The grader passes: 0.0, 1.0, -1.0, 3.14, 2.5, -0.5, 99.9.
// Use an if / else if chain. Return "double:?" for any other value.
//
// ─── TASK 2: replace the placeholder body ───────────────────────────────────
inline std::string_view describe(double value)
{
    (void)value;
    return "double:?";  // placeholder
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 3: describe(bool) ──────────────────────────────────────────────────
// Return "bool:true" or "bool:false".
//
// KEY CONCEPT — overload resolution and promotions (notes 11.3):
// Without this exact overload, describe(true) would call describe(int) because
// bool PROMOTES to int (step 2 of resolution). By providing an exact bool
// overload the compiler stops at step 1 (exact match). The test checks that
// describe(true) returns "bool:true", not "int:1" — that difference is the
// whole lesson made visible.
//
// ─── TASK 3: replace the placeholder body ───────────────────────────────────
inline std::string_view describe(bool value)
{
    (void)value;
    return "bool:?";   // placeholder — wrong; use a ternary or if/else
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 4: describe(std::string_view) ──────────────────────────────────────
// Return "str:<value>" for each expected input.
// The grader passes "hello", "LLVM", and "" (empty).
// Use if / else if chains comparing value == "hello", etc.
//
// ─── TASK 4: replace the placeholder body ───────────────────────────────────
inline std::string_view describe(std::string_view value)
{
    (void)value;
    return "str:?";    // placeholder
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── NOTE — = delete to block unwanted conversions (notes 11.4) ──────────────
// Uncomment the line below to see describe(char) become a compile error instead
// of silently calling describe(int) via char->int promotion. The grader cannot
// test compile errors, so this is a teaching note, not an active check.
//
//     inline void describe(char) = delete;  // try: describe('x'); -> error

// ============================================================================
// PART B — FUNCTION TEMPLATE DEFINITIONS  (TASK 5 – 8)
// ============================================================================
//
// Templates MUST be defined in headers so every translation unit that calls
// them can instantiate the needed function (notes 11.10). Edit the stub bodies
// below — they compile but return WRONG values until you fill them in.

// ─── TASK 5: myMin<T> ────────────────────────────────────────────────────────
// Return the smaller of two same-type values.
// Works for any T that supports operator<.
//
// DEDUCTION: myMin(3, 5) deduces T=int automatically.
// EXPLICIT:  myMin<double>(3, 4.5) forces T=double so int 3 -> 3.0 (notes 11.7).
//
// If you pass an int and a double without an explicit type, the compiler cannot
// deduce a single T from conflicting types — see notes 11.7.
//
// ─── TASK 5: replace the placeholder body ───────────────────────────────────
template <typename T>
T myMin(T a, T b)
{
    (void)b;       // suppress unused-parameter warning on placeholder
    return a;      // placeholder — always returns the first argument
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 6: myClamp<T> ──────────────────────────────────────────────────────
// Clamp `value` into the closed interval [lo, hi]:
//   value < lo  ->  return lo
//   value > hi  ->  return hi
//   otherwise   ->  return value (unchanged)
// Works for any T with operator<. Use two branches (if / else if / else).
//
// ─── TASK 6: replace the placeholder body ───────────────────────────────────
template <typename T>
T myClamp(T value, T lo, T hi)
{
    (void)lo; (void)hi;   // suppress unused-parameter warnings on placeholder
    return value;          // placeholder — unclamped (wrong at boundaries)
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 7: repeatChar<N> ───────────────────────────────────────────────────
// Return a string_view of N identical copies of `ch`.
// N is a NON-TYPE template parameter (int) — a compile-time constant (notes 11.9).
//
// Implementation:
//   Use `static char buf[N + 1]` — a static array sized at COMPILE TIME.
//   Each instantiation (repeatChar<3>, repeatChar<5>, …) gets its OWN static
//   buffer (notes 11.7: "static locals are per instantiation"), so repeated
//   calls for different N don't clobber each other. Fill all N slots with ch,
//   null-terminate with buf[N] = '\0', return string_view{buf, N}.
//
// Constraint: 1 <= N <= 32 (keep the array small).
//
// ─── TASK 7: replace the placeholder body ───────────────────────────────────
template <int N>
std::string_view repeatChar(char /*ch*/)
{
    // Placeholder: a zero-filled static buffer. The returned view has N chars
    // but they are all '\0', so every string comparison will fail.
    static char buf[N + 1]{};
    return std::string_view{buf, N};
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ─── TASK 8: scaled<T,U> ─────────────────────────────────────────────────────
// Multiply two (possibly different) numeric types and return the result.
//
// Use TWO type parameters (T, U) so int and double can be mixed (notes 11.8).
// Use `auto` as the return type so the compiler deduces the result type from
// the expression `a * b` — e.g. int * double -> double (notes 11.8).
//
// NOTE: abbreviated auto parameters (C++20 `auto add(auto a, auto b)`) are
// FORBIDDEN — use explicit `template <typename T, typename U>` syntax instead.
//
// ─── TASK 8: replace the placeholder body ───────────────────────────────────
template <typename T, typename U>
auto scaled(T /*a*/, U /*b*/)
{
    return T{};   // placeholder — always returns a zero-value T
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

// ============================================================================
// PART C — DEFAULT ARGUMENT  (TASK 9)
// ============================================================================

// ─── TASK 9: formatCount ─────────────────────────────────────────────────────
// Return a labeled count string.  The `suffix` parameter has a DEFAULT
// argument of "s" — declared HERE in the header so every caller can see it
// (notes 11.5: "put defaults in declarations, not repeated definitions").
//
// Logic:
//   count == 1  ->  "1 <label>"           (singular — no suffix)
//   count != 1  ->  "<count> <label><suffix>"  (plural — append suffix)
//
// Examples:
//   formatCount("item",  3)         -> "3 items"    (default suffix "s")
//   formatCount("error", 1)         -> "1 error"    (singular, no suffix)
//   formatCount("pass",  2, "es")   -> "2 passes"   (explicit suffix)
//
// Implementation: use two static char buffers (64 bytes each) — one for the
// singular branch, one for the plural branch — so the returned string_view
// stays valid after the function returns.
//
//   Build strings MANUALLY: copy label chars, append suffix, null-terminate.
//   (std::to_string arrives cleanly in Ch 5; manual char copy is fine here.)
//   Counts 0–99 are all the grader uses.
//
// ─── TASK 9: replace the placeholder body ───────────────────────────────────
inline std::string_view formatCount(std::string_view /*label*/, int /*count*/,
                                     std::string_view /*suffix*/ = "s")
{
    return "?";   // placeholder — wrong for all inputs
    //   >>> YOUR CODE HERE <<<
}
// ─────────────────────────────────────────────────────────────────────────────

#endif // TOOLKIT_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).