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:
void instrument(Function& function); // overloaded for Function
void instrument(BasicBlock& block); // overloaded for BasicBlock
void instrument(Instruction& instr); // overloaded for Instructionand 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
describe(int)— the int overload. Return"int:<value>"for each expected input (0, 1, -1, 42, -99, 100) using aswitchstatement. Return"int:?"for any other value.describe(double)— the double overload. Return"double:<value>"for each expected input using anif/else ifchain (doubles cannot be used asswitchcase constants). Return"double:?"for unknown values.describe(bool)— the bool overload. Return"bool:true"or"bool:false". This is the key overload-resolution task: without it,describe(true)callsdescribe(int)becauseboolpromotes toint(notes 11.3). The test checks for"bool:true", not"int:1"— that difference is the lesson made visible. Use a ternary?:orif/else.describe(std::string_view)— the string overload. Return"str:<value>"for"hello","LLVM", and""(empty). Use anif/else ifchain. Note: the grader passes"hello"sv(thesvsuffix makes the argument already astring_view— an exact match at step 1) to show why this matters.myMin<T>— a function template. Write a single template that returns the smaller of two same-type values. Useoperator<and a ternary. Test it with bothintanddouble. The grader also callsmyMin<double>(3, 4.5)to exercise explicit template arguments — the int3is converted to3.0because you forcedT = double(notes 11.7).myClamp<T>— another template with boundary logic. Clampvalueinto[lo, hi]: ifvalue < loreturnlo, ifvalue > hireturnhi, otherwise returnvalueunchanged. Anif/else if/elsechain is the right shape. Works for int, double, and any T with<.repeatChar<N>— non-type template parameter. Return astring_viewofNidentical copies ofch.Nis anintnon-type template parameter — a compile-time constant that sizes astatic char buf[N + 1]array. Each distinctN(e.g.repeatChar<3>vsrepeatChar<5>) is a separate instantiation with its own static buffer (notes 11.9, 11.7).scaled<T,U>— two-type template with auto return. Multiplya(type T) byb(type U) and return the result. Use two type parameters sointanddoublearguments can be mixed without explicit casts. Useautoas the return type so the compiler deduces it froma * b— forint * doublethe result isdouble, preserving the fractional part (notes 11.8).formatCount— default argument. Return a count string: singular ("1 item") whencount == 1, plural ("3 items") otherwise. Thesuffixparameter 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 returnedstring_viewvalid.
Success criteria
describe(true) == "bool:true"anddescribe(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 returnamyMin<double>(3, 4.5) ~= 2.7— explicit template argument must forceT=doublesointis converted before comparison (notes 11.7)myClamp(0, 1, 10) == 1— below lo must clamp to lo, not pass throughmyClamp(0, 3, 3) == 3— lo == hi edge case must clamp to that single valuerepeatChar<3>('*') == "***"— three chars, correct content, size == 3scaled(3, 2.5) ~= 7.5—autoreturn must bedouble, not truncatedintformatCount("item", 1) == "1 item"— singular, no suffixformatCount("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
bool→intpromotion (notes 11.3) = deletefor 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
Tfrom arguments (11.7) - Explicit template arguments —
myMin<double>(3, 4.5)forcesT=double(11.7) - Multiple template type parameters —
template <typename T, typename U>for mixed-type operations (notes 11.8) autoreturn type — deduced from the return expression (notes 11.8)- Non-type template parameters —
template <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),constcorrectness (Ch 5),inline(Ch 7),static_cast(Ch 10), header guards (Ch 2)
Constraints
Allowed (Chapter ≤ 11):
template <typename T>andtemplate <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–8std::string_view,const,auto,static_cast,inline,static<string_view>,<cmath>(forstd::absin 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 herestd::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
.cppfile) — 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
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 artifactsmake 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
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:
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
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
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
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
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:
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 thatdescribe('A')becomes a compile error instead of silently callingdescribe(int). (Just uncomment the line instarter/toolkit.h.) - Extend
myMinto amyMax, then buildmyClampby callingmyMax(lo, myMin(value, hi))— shows how templates compose just like overloads do. - Add a third type parameter to
scaledand generalize to three-value multiply (compare with the three-template-type overload in notes 11.8). - Replace the manual integer-to-char conversion in
formatCountwithstd::to_string(Ch 5) orstd::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.
// ============================================================================
// 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
Try the lab first — the learning is in the attempt.
// ============================================================================
// toolkit.h — the generic toolkit mini-library (Chapter 11 SOLUTION)
// ----------------------------------------------------------------------------
// One complete, correct, warning-clean implementation. Peek here only after
// you've taken a real swing at starter/toolkit.h — the learning is in wiring
// the overloads and templates yourself, then comparing.
//
// Key concepts demonstrated (with notes references):
// TASK 1–4 overloaded describe() — one name, four resolutions (11.1, 11.2)
// TASK 3 exact bool overload prevents silent promotion (11.3)
// TASK 5 myMin<T>: single-type template + explicit template arg (11.7)
// TASK 6 myClamp<T>: template with boundary conditions (11.6)
// TASK 7 repeatChar<N>: non-type template parameter (11.9),
// per-instantiation static local (11.7)
// TASK 8 scaled<T,U>: two-type template, auto return (11.8)
// TASK 9 formatCount: default argument in declaration (11.5)
// ============================================================================
#ifndef TOOLKIT_H
#define TOOLKIT_H
#include <string_view> // std::string_view (Ch 5)
// ============================================================================
// PART A — OVERLOADED describe() FUNCTIONS
// ============================================================================
// ─── TASK 1: describe(int) ───────────────────────────────────────────────────
// Exact match for int arguments (notes 11.2 step 1).
// The switch maps each grader-supplied value to a labeled literal.
// Returning a string literal is safe: string literals have static storage
// duration (their lifetime = the whole program), so string_view is valid.
// (A preview of lifetime reasoning — formally Chapter 12.)
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:?";
}
}
// ─── TASK 2: describe(double) ────────────────────────────────────────────────
// Exact match for double arguments (notes 11.2).
// if/else if because double values cannot be used as switch case constants.
// Comparing doubles with == is sound here: the caller passes exact literals
// that round-trip without arithmetic rounding.
inline std::string_view describe(double value)
{
if (value == 0.0) return "double:0.0";
else if (value == 1.0) return "double:1.0";
else if (value == -1.0) return "double:-1.0";
else if (value == 3.14) return "double:3.14";
else if (value == 2.5) return "double:2.5";
else if (value == -0.5) return "double:-0.5";
else if (value == 99.9) return "double:99.9";
else return "double:?";
}
// ─── TASK 3: describe(bool) ──────────────────────────────────────────────────
// CRITICAL: without this overload, describe(true) calls describe(int) because
// bool PROMOTES to int (notes 11.3: promotions beat conversions at step 2).
// With this exact overload the compiler stops at step 1 — no promotion needed.
//
// CS6340 tie-in: LLVM APIs use this pattern to prevent callers from accidentally
// passing an int flag where a bool was expected, catching the mix-up at compile
// time rather than at a silent runtime misbehavior.
inline std::string_view describe(bool value)
{
return value ? "bool:true" : "bool:false";
}
// ─── = delete DEMO (notes 11.4) ──────────────────────────────────────────────
// Uncomment to see describe('x') become a compile error instead of silently
// calling describe(int) via char->int promotion:
//
// inline void describe(char) = delete;
//
// The "use of deleted function" error is the whole point of notes 11.4 —
// deleted overloads make dangerous implicit conversions a compile-time failure.
// ─── TASK 4: describe(std::string_view) ──────────────────────────────────────
// Exact match for string_view arguments (string literals convert to string_view).
inline std::string_view describe(std::string_view value)
{
if (value == "hello") return "str:hello";
else if (value == "LLVM") return "str:LLVM";
else if (value == "") return "str:";
else return "str:?";
}
// ============================================================================
// PART B — FUNCTION TEMPLATE DEFINITIONS
// ============================================================================
// ─── TASK 5: myMin<T> ────────────────────────────────────────────────────────
// T is deduced from the arguments for normal calls: myMin(3, 5) -> T=int.
// For mixed types the caller must be explicit: myMin<double>(3, 4.5) forces
// T=double and converts the int 3 to 3.0 (notes 11.7).
//
// The `return (a < b) ? a : b;` pattern is the textbook min: one `<` comparison,
// no branch that could be surprising for types where `==` is not defined.
template <typename T>
T myMin(T a, T b)
{
return (a < b) ? a : b; // a < b means a is the smaller one
}
// ─── TASK 6: myClamp<T> ──────────────────────────────────────────────────────
// The three cases are mutually exclusive: value < lo, value > hi, or in range.
// An if / else if / else chain maps directly to those cases.
// Works for int, double, char, or any T where < makes sense.
template <typename T>
T myClamp(T value, T lo, T hi)
{
if (value < lo)
return lo; // below lower bound -> clamp to lo
else if (hi < value)
return hi; // above upper bound -> clamp to hi
else
return value; // within [lo, hi] -> pass through unchanged
}
// ─── TASK 7: repeatChar<N> ───────────────────────────────────────────────────
// NON-TYPE template parameter N is a compile-time integer (notes 11.9).
// `static char buf[N + 1]` is dimensioned AT COMPILE TIME — each instantiation
// gets its own separate static array (notes 11.7: "static locals are per
// instantiation"), so repeatChar<3> and repeatChar<5> never share storage.
//
// We fill the buffer on EVERY call (ch is a parameter, so it can vary between
// calls to the same instantiation). Because buf is static the fill re-runs
// each call — that's correct: the caller's ch might differ from a previous call.
template <int N>
std::string_view repeatChar(char ch)
{
static char buf[N + 1]; // compile-time sized; each N has its own array
for (int i = 0; i < N; ++i)
buf[i] = ch; // fill all N slots with the requested character
buf[N] = '\0'; // null-terminate so it's a valid C-string too
return std::string_view{buf, N};
}
// ─── TASK 8: scaled<T,U> ─────────────────────────────────────────────────────
// Two type parameters allow mixed-type calls without requiring explicit casts
// from the caller (notes 11.8: "use multiple type parameters when different
// types are expected").
//
// `auto` return type deduces the result type from `a * b` (notes 11.8: "use
// auto return type when result type differs"). For int * double the deduced
// type is double — the wider type, which is what the caller expects.
//
// Note: abbreviated function template syntax (C++20's `auto add(auto a, auto b)`)
// would be shorter but is not yet taught — explicit `template <typename T, typename U>`
// is the required form (notes 11.8 covers abbreviation but CLAUDE.md scope forbids it).
template <typename T, typename U>
auto scaled(T a, U b)
{
return a * b; // return type deduced from the expression (auto -> T*U result type)
}
// ============================================================================
// PART C — DEFAULT ARGUMENT
// ============================================================================
// ─── TASK 9: formatCount ─────────────────────────────────────────────────────
// The default `suffix = "s"` is declared HERE (notes 11.5: "put defaults in
// visible declarations"). Only ONE of the two branches runs per call, so we
// use two static buffers — one for singular, one for plural — both safe as
// string_view backing storage (static lifetime, no heap allocation).
//
// Manual integer printing: handle 0–99 (all the grader uses) with two-digit
// decomposition. For a real project, std::to_string (Ch 5) or std::snprintf
// (Ch 28) would be cleaner; staying manual here avoids out-of-scope includes.
inline std::string_view formatCount(std::string_view label, int count,
std::string_view suffix = "s")
{
// ── Singular branch (count == 1) ─────────────────────────────────────────
if (count == 1)
{
static char singular[64];
int i = 0;
singular[i++] = '1';
singular[i++] = ' ';
for (std::size_t k = 0; k < label.size() && i < 62; ++k)
singular[i++] = label[k];
singular[i] = '\0';
return std::string_view{singular, static_cast<std::size_t>(i)};
}
// ── Plural branch (count != 1) ────────────────────────────────────────────
static char plural[64];
int i = 0;
// Write count (0–99)
int c = (count < 0) ? -count : count; // use magnitude (grader never passes negative)
if (c >= 10)
plural[i++] = static_cast<char>('0' + (c / 10) % 10);
plural[i++] = static_cast<char>('0' + c % 10);
plural[i++] = ' ';
// Append label
for (std::size_t k = 0; k < label.size() && i < 60; ++k)
plural[i++] = label[k];
// Append suffix (e.g. "s" or "es")
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)};
}
#endif // TOOLKIT_H
// ============================================================================
// tests/tests.cpp — the automated grader for the generic toolkit (Ch 11)
// ----------------------------------------------------------------------------
// A no-framework unit-test harness (same style as drills/CLAUDE.md).
// Includes ../toolkit.h and calls every function across many inputs.
//
// This is Style B2 (header-only): there are no .cpp implementation files.
// The Makefile swaps implementations purely by include path — `-Istarter`
// (make test) vs `-Isolution` (make test-solution) — so this same file
// #includes EITHER starter/toolkit.h OR solution/toolkit.h. The template
// definitions in toolkit.h are always visible to this file, so template
// instantiation happens here at compile time (notes 11.10).
//
// DO NOT EDIT THIS FILE — it is the contract.
//
// Coverage map:
// describe(int) — TASK 1: exact int overload (notes 11.2)
// describe(double) — TASK 2: exact double overload (notes 11.2)
// describe(bool) — TASK 3: bool overload prevents int-promotion (11.3)
// describe(string_view) — TASK 4: string_view overload (notes 11.2)
// myMin<T> — TASK 5: single-type template + explicit arg (11.6, 11.7)
// myClamp<T> — TASK 6: template with boundary logic (11.6, 11.7)
// repeatChar<N> — TASK 7: non-type template parameter (notes 11.9)
// scaled<T,U> — TASK 8: two-type template + auto return (notes 11.8)
// formatCount — TASK 9: default arguments (notes 11.5)
// ============================================================================
#include <iostream>
#include <cmath> // std::abs for floating-point comparison
#include "toolkit.h" // found via -Istarter or -Isolution (Style B2)
// Convenience: std::string_view literal suffix (C++17)
using namespace std::string_view_literals;
static int fails = 0;
// CHECK: exact boolean condition; on failure print expression + line number.
#define CHECK(cond) \
do { if(!(cond)){ std::cerr << "FAIL: " #cond " @line " << __LINE__ << "\n"; ++fails; } } while(0)
// CHECK_NEAR: floating-point equality within a small epsilon.
#define CHECK_NEAR(got, want) \
do { auto g_=(got); auto w_=(want); \
if(std::abs(static_cast<double>(g_) - static_cast<double>(w_)) > 1e-9){ \
std::cerr << "FAIL: " #got " ~= " #want \
<< " (got " << g_ << ", want " << w_ << ") @line " << __LINE__ << "\n"; \
++fails; } } while(0)
int main()
{
// ── Task 1: describe(int) — exact int overload ───────────────────────────
// These must call describe(int), NOT describe(double) or describe(bool).
// If the int overload is missing or wrong, the label will not start "int:".
CHECK(describe(0) == "int:0");
CHECK(describe(1) == "int:1");
CHECK(describe(-1) == "int:-1");
CHECK(describe(42) == "int:42");
CHECK(describe(-99) == "int:-99");
CHECK(describe(100) == "int:100");
// ── Task 2: describe(double) — exact double overload ─────────────────────
// An integer literal like `0` would pick describe(int); the `0.0` suffix
// forces a double literal and the double overload (notes 11.2 / 11.3).
CHECK(describe(0.0) == "double:0.0");
CHECK(describe(1.0) == "double:1.0");
CHECK(describe(-1.0) == "double:-1.0");
CHECK(describe(3.14) == "double:3.14");
CHECK(describe(2.5) == "double:2.5");
CHECK(describe(-0.5) == "double:-0.5");
CHECK(describe(99.9) == "double:99.9");
// ── Task 3: describe(bool) — bool overload beats int promotion ───────────
// KEY RESOLUTION CHECK: bool promotes to int (notes 11.3), so without the
// bool overload describe(true) would return "int:1". The exact bool overload
// must fire instead. This makes the promotion rule VISIBLE.
CHECK(describe(true) == "bool:true");
CHECK(describe(false) == "bool:false");
// Verify the bool overload does NOT delegate to the int path:
CHECK(describe(true) != "int:1"); // must be "bool:true", not int path
CHECK(describe(false) != "int:0"); // must be "bool:false", not int path
// ── Task 4: describe(std::string_view) — string overload ─────────────────
// We pass string_view values (using the sv literal suffix) rather than raw
// string literals. Why? A plain "hello" has type const char*, which converts
// to bool (a standard conversion, step 3) before converting to string_view
// (a user-defined conversion, step 4) — so without the sv suffix the bool
// overload would win, hiding the string overload entirely (notes 11.3).
// The sv suffix makes the argument ALREADY a string_view — exact match, step 1.
CHECK(describe("hello"sv) == "str:hello");
CHECK(describe("LLVM"sv) == "str:LLVM");
CHECK(describe(""sv) == "str:"); // edge: empty string_view
// ── Task 5: myMin<T> — single-type template + explicit template arg ───────
// Deduction cases: compiler infers T from the argument types.
CHECK(myMin(3, 5) == 3); // T=int deduced; 3 < 5 -> 3
CHECK(myMin(5, 3) == 3); // T=int; 3 < 5 -> 3
CHECK(myMin(0, 0) == 0); // edge: equal values
CHECK(myMin(-1, 1) == -1); // T=int, negative
CHECK(myMin(-5, -3) == -5); // edge: both negative; -5 < -3
CHECK_NEAR(myMin(1.5, 2.5), 1.5); // T=double, deduced
CHECK_NEAR(myMin(3.0, 3.0), 3.0); // edge: equal doubles
// Explicit template argument (notes 11.7): myMin<double>(int, double)
// forces T=double so the int 3 is converted to 3.0 before comparison.
CHECK_NEAR(myMin<double>(3, 4.5), 3.0); // explicit T=double, int->double
CHECK_NEAR(myMin<double>(10, 2.7), 2.7); // explicit T=double
// ── Task 6: myClamp<T> — template with boundary clamping ─────────────────
// Value already in range:
CHECK(myClamp(5, 1, 10) == 5); // T=int, in range, pass-through
CHECK(myClamp(1, 1, 10) == 1); // edge: exactly lo, pass-through
CHECK(myClamp(10, 1, 10) == 10); // edge: exactly hi, pass-through
// Value below lo:
CHECK(myClamp(0, 1, 10) == 1); // below lo -> clamp to lo
CHECK(myClamp(-5, 0, 3) == 0); // negative value clamped to 0
// Value above hi:
CHECK(myClamp(15, 1, 10) == 10); // above hi -> clamp to hi
CHECK(myClamp(100, 0, 3) == 3); // far above hi
// Double version:
CHECK_NEAR(myClamp(0.5, 1.0, 5.0), 1.0); // below lo -> 1.0
CHECK_NEAR(myClamp(3.0, 1.0, 5.0), 3.0); // in range
CHECK_NEAR(myClamp(7.5, 1.0, 5.0), 5.0); // above hi -> 5.0
// Edge: lo == hi -> always clamped to that single value
CHECK(myClamp(0, 3, 3) == 3);
CHECK(myClamp(5, 3, 3) == 3);
// ── Task 7: repeatChar<N> — non-type template parameter ──────────────────
// Each distinct N is a SEPARATE instantiation with its OWN static buf (11.9).
CHECK(repeatChar<1>('x') == "x");
CHECK(repeatChar<3>('*') == "***");
CHECK(repeatChar<5>('-') == "-----");
CHECK(repeatChar<4>('a') == "aaaa");
CHECK(repeatChar<2>(' ') == " "); // edge: spaces
// Size must match N exactly:
CHECK(repeatChar<3>('x').size() == 3);
CHECK(repeatChar<5>('!').size() == 5);
// Different N values -> truly different instantiations: sizes differ.
CHECK(repeatChar<1>('z').size() != repeatChar<3>('z').size());
// ── Task 8: scaled<T,U> — two-type template, auto return ─────────────────
// int * int -> int
CHECK(scaled(4, 3) == 12); // T=int, U=int -> int
CHECK(scaled(0, 99) == 0); // edge: zero factor
CHECK(scaled(-2, 5) == -10); // negative
// int * double -> double (auto return captures the wider type)
CHECK_NEAR(scaled(3, 2.5), 7.5); // T=int, U=double -> double 7.5
CHECK_NEAR(scaled(2, 1.5), 3.0);
// double * double -> double
CHECK_NEAR(scaled(1.5, 2.0), 3.0); // T=double, U=double
CHECK_NEAR(scaled(0.5, 4.0), 2.0);
// ── Task 9: formatCount — default argument ────────────────────────────────
// Singular (count == 1): no suffix appended.
CHECK(formatCount("item", 1) == "1 item");
CHECK(formatCount("error", 1) == "1 error");
CHECK(formatCount("test", 1) == "1 test");
// Plural with default suffix "s":
CHECK(formatCount("item", 3) == "3 items"); // "item" + "s"
CHECK(formatCount("error", 5) == "5 errors"); // "error" + "s"
CHECK(formatCount("bug", 2) == "2 bugs"); // "bug" + "s"
CHECK(formatCount("item", 0) == "0 items"); // edge: 0 is plural
// Explicit suffix override:
CHECK(formatCount("pass", 2, "es") == "2 passes"); // "pass" + "es"
CHECK(formatCount("match", 3, "es") == "3 matches"); // "match" + "es"
CHECK(formatCount("fox", 2, "es") == "2 foxes"); // "fox" + "es"
// Singular with explicit suffix — suffix still ignored for count==1:
CHECK(formatCount("pass", 1, "es") == "1 pass");
CHECK(formatCount("item", 1, "xyz") == "1 item");
if (!fails)
std::cout << "PASS \xe2\x9c\x85 all toolkit checks passed.\n";
else
std::cerr << "\nFAIL \xe2\x9d\x8c " << fails << " check(s) failed — fix the TASK blocks.\n";
return fails ? 1 : 0;
}
# Chapter 11 — Function Overloading & Function Templates
# Project: the generic toolkit mini-library
# Style B2 grader (header-only). TABS, not spaces.
#
# Targets follow the drills/CLAUDE.md Makefile contract.
#
# Style B2: the learner edits starter/toolkit.h — all function bodies are
# inline in the header (necessary for templates, convenient for non-templates
# in the same file). The grader (tests/tests.cpp) does #include "toolkit.h"
# and picks up the right version via -Istarter or -Isolution. No separate
# .cpp files — same pattern as Chapter 14.
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 header (starter/toolkit.h).
# This compiles the tests against the starter so that any syntax error in the
# header is surfaced immediately. Tests will FAIL at runtime (RED) until
# TASK blocks are filled in — that's expected and correct.
build:
$(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/run_starter
@echo "OK \xe2\x9c\x85 starter/toolkit.h compiles. Now run: make test"
# run — show the learner what RED looks like.
run: build
@./tests/run_starter || true
# test — grade the LEARNER's header (RED until TASK blocks are filled in).
test:
$(CXX) $(CXXFLAGS) -Istarter tests/tests.cpp -o tests/run_starter
@./tests/run_starter || echo "FAIL \xe2\x9d\x8c fill in the TASK blocks in starter/toolkit.h until every check passes."
# solution — compile and run the REFERENCE header as a quick sanity demo.
solution:
$(CXX) $(CXXFLAGS) -Isolution tests/tests.cpp -o tests/run_solution
@./tests/run_solution
# test-solution — proof the lab is solvable: the reference MUST pass every check.
test-solution:
$(CXX) $(CXXFLAGS) -Isolution tests/tests.cpp -o tests/run_solution
@./tests/run_solution
clean:
rm -f tests/run_starter tests/run_solution tests/run a.out
rm -rf tests/run_starter.dSYM tests/run_solution.dSYM
make test locally
(see “Build & run locally” above).