Name-Badge / Greeting Formatter
You're writing the text engine behind a conference name-badge printer. Given a
person's first and last name, it assembles every string the badge needs: the org
header across the top, the full name "Ada Lovelace", a friendly greeting
"Hello, my name is Ada Lovelace", a login "Ada.Lovelace", monogram initials
"A.L.", and the name's length for layout. It's a little library of six small,
const-correct functions — no main, no I/O — graded by unit tests.
The whole chapter is in the types. Every input a function only reads is a
std::string_view (a cheap read-only window — no copying), every function that
builds text returns an owning std::string, and the org name, separator, and
badge width are constexpr named constants instead of literals sprinkled
through the logic. And because Chapter 5 has no loops yet, you build each result
by assembling strings — concatenate with +=, peek the first letter with
.front(), measure with .size() — never by scanning character-by-character.
Your tasks
badgeHeader()— return the org name. It's thekOrgNameconstant (astd::string_view); the function returns astd::string, so you copy the view into an owning string:std::string { kOrgName }.fullName(first, last)— build"First Last"(one space between) by concatenating into astd::string.greeting(first, last)— build"Hello, my name is First Last". ReusefullName— don't re-join the names by hand.username(first, last)— build"first.last", joining with thekUsernameSepconstant (not a hard-coded'.').initials(first, last)— build"F.L."from each name's.front(). Edge case:.front()on an empty view is undefined behavior, so guard each name withif (name.size() > 0)and contribute nothing for an empty one.nameLength(first, last)— return the length of the full name via.size(). It's unsigned, sostatic_cast<int>(...)it.
Success criteria
make test prints PASS ✅ all checks and exits 0. The grader in
tests/tests.cpp calls every function with normal inputs
and edge cases — empty names (initials("", "") == ""), single-character
names (fullName("A","B") == "A B"), and the just-a-space full name
(nameLength("", "") == 1). Until you fill in the tasks, the stubs return junk
and make test shows FAIL ❌ listing each failing CHECK and its line.
Turning that red into green is the whole exercise.
Concepts practiced
constexprnamed constants, including aconstexpr std::string_viewstring label (5.1, 5.2, 5.6, 5.8)- Killing magic numbers / literals by naming them (the separator, the width) (5.2)
std::string— owning, growable text you build and return (5.7)std::string_view— cheap, read-only parameters that accept literals, strings, or views (5.8, 5.9)- Why a view does not implicitly become an owning string — explicit
std::string { view }(5.8) .front(),.size(),.empty(), and string concatenation with+=- Reused from earlier chapters: functions / headers / header guards (Ch 2),
ifandstatic_cast(Ch 4), value-initialization with{}(Ch 1)
Constraints
- Implement the bodies only. Don't touch
badge.h— it's the contract the grader relies on (function names, parameter and return types). - Read-only parameters stay
std::string_view; text you build is returned as astd::string. Don't change the signatures. - Use the named constants from
badge.h(kOrgName,kUsernameSep) instead of re-typing their literal values. - No loops (Chapter 8) — every task is fixed string assembly. Allowed tools:
+/+=,.front(),.size(),ifwith a relational test (> 0),static_cast. - Don't add I/O — this is a pure library. The grader calls your functions directly.
Build & run locally
make # compile starter/badge.cpp (it builds out of the box)
make test # grade YOUR code against the unit tests
make solution # run the grader against the reference solution
make run # same as `make test` (a library has no program of its own)
make cleanHints
Task 1 — view vs. owning string
kOrgName is a std::string_view, but the return type is std::string. A view
does not silently become an owning string (that would hide an expensive copy),
so make the copy explicit: return std::string { kOrgName };.
Tasks 2 & 4 — concatenating into a std::string
Seed an owning string from the first view, then append onto it. A
std::string_view and a char both append with +=:
std::string result { first };
result += ' '; // a char (Task 2: the space)
result += last; // a string_view
return result;For Task 4 the join character is the named constant: result += kUsernameSep;.
Task 3 — compose, don't repeat
Build the fixed prefix, then append the result of fullName:
std::string result { "Hello, my name is " };
result += fullName(first, last); // fullName returns a std::string
return result;Task 5 — the empty-name guard
.front() reads the first character but is undefined behavior on an empty
view. Guard each name independently so an empty one simply contributes nothing:
std::string result {};
if (first.size() > 0) { result += first.front(); result += kUsernameSep; }
if (last.size() > 0) { result += last.front(); result += kUsernameSep; }
return result;(.empty() would read more nicely, but > 0 keeps us to the relational
operators already seen in Chapter 4.)
Task 6 — size() is unsigned
.size() returns std::size_t (unsigned). Returning it directly as int warns
under -Wall -Wextra, so convert deliberately:
return static_cast<int>(fullName(first, last).size());
Stretch goals
- Make the username lowercase (
"Ada.Lovelace"→"ada.lovelace"). That needs to walk each character, i.e. a loop (Chapter 8) plusstd::tolower. - Add
padBadge(text)that centerstextinsidekBadgeWidthcolumns — theconstexpr int kBadgeWidthis already declared inbadge.hwaiting for you. - Trim surrounding spaces from a name with
string_view's.remove_prefix()/.remove_suffix()before building (notes 5.9) — needs a loop to find the spaces. - Validate input and reject empty both names with an error path (Chapter 9's
assert/ error handling).
// ============================================================================
// Chapter 5 — Constants and Strings · Project: Name-Badge / Greeting Formatter
// starter/badge.cpp — YOU EDIT THIS FILE.
// ============================================================================
//
// Fill in the six >>> YOUR CODE HERE <<< blocks. Each maps 1:1 to a TASK in the
// README. The function signatures and the named constants come from ../badge.h
// (already complete) — read it first; it explains WHY each type was chosen.
//
// make build compile this file (it already compiles — stubs return junk)
// make test grade YOUR code -> RED until you finish, then GREEN
// make solution / make test-solution the reference, if you get stuck
//
// Right now every function returns a placeholder, so `make build` succeeds but
// `make test` FAILS. Turning that red into green is the exercise.
//
// REMINDERS for this chapter:
// * std::string_view params are read-only windows — cheap to pass, no copy.
// * Build text by CONCATENATING with + (or +=) into a std::string you return.
// * A std::string_view can be appended to a std::string directly.
// * .front() reads the first char; .size() gives the length (UNSIGNED).
// * NO loops — every task is a fixed bit of string assembly.
#include "../badge.h" // the contract: signatures + the kOrg/kUsername/kBadge constants
// ─── TASK 1: return the organization name ───────────────────────────────────
// Return the kOrgName constant from badge.h. It is a std::string_view; this
// function returns a std::string, so the view is copied into an owning string
// on the way out (that is exactly what the caller wants — its own text).
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
std::string badgeHeader()
{
return {}; // STUB: empty string. Replace with the real header.
}
// ─── TASK 2: build "First Last" ─────────────────────────────────────────────
// Concatenate first, a single space " ", and last into one std::string.
// Hint: std::string result { first }; then result += " "; then result += last;
// (a std::string_view appends straight onto a std::string with +=).
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
std::string fullName(std::string_view first, std::string_view last)
{
(void)first; // STUB: silence "unused parameter" until you use them.
(void)last;
return {}; // replace with "First Last".
}
// ─── TASK 3: build "Hello, my name is First Last" (REUSE fullName) ──────────
// Don't re-assemble the name by hand — call fullName(first, last) and build the
// greeting around it. Composing small functions is the point.
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
std::string greeting(std::string_view first, std::string_view last)
{
(void)first;
(void)last;
return {}; // replace with "Hello, my name is First Last".
}
// ─── TASK 4: build "first<sep>last" using kUsernameSep ──────────────────────
// Join first and last with the kUsernameSep constant ('.') from badge.h —
// do NOT hard-code a '.' here; use the named constant so the separator lives in
// one place. Result for ("Ada","Lovelace") is "Ada.Lovelace".
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
std::string username(std::string_view first, std::string_view last)
{
(void)first;
(void)last;
return {}; // replace with "first.last".
}
// ─── TASK 5: build initials "F.L." with an EMPTY-name guard ─────────────────
// For each name: IF it is non-empty (.size() > 0), append its .front() character
// followed by kUsernameSep. If it is empty, append nothing for it (do NOT call
// .front() on an empty view — that is undefined behavior). Use an `if` guard.
// ("Ada","Lovelace") -> "A.L." ("","Lovelace") -> "L." ("","") -> ""
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
std::string initials(std::string_view first, std::string_view last)
{
(void)first;
(void)last;
return {}; // replace with the initials string.
}
// ─── TASK 6: report the length of the full name with .size() ────────────────
// Build the full name (reuse fullName!), then return its .size(). Because
// .size() is UNSIGNED (std::size_t) and this returns int, convert it:
// return static_cast<int>(theName.size());
//
// >>> YOUR CODE HERE <<<
//
// ────────────────────────────────────────────────────────────────────────────
int nameLength(std::string_view first, std::string_view last)
{
(void)first;
(void)last;
return 0; // replace with the real length.
}
Try the lab first — the learning is in the attempt.
// ============================================================================
// Chapter 5 — Constants and Strings · Project: Name-Badge / Greeting Formatter
// solution/badge.cpp — REFERENCE SOLUTION (complete, correct, commented).
// ============================================================================
//
// One clean way to satisfy every test. Peek only after you've tried — the
// learning is in getting YOUR starter/badge.cpp to pass `make test` first.
//
// Throughline of the chapter, visible in the types below:
// * Every read-only input is a std::string_view (cheap window, no copy).
// * Every function that produces text returns an OWNING std::string.
// * The separator, org name, and width are named constexpr constants from
// ../badge.h — never re-typed as bare literals in the logic.
// * No loops: each result is a short, fixed piece of string assembly.
#include "../badge.h"
// TASK 1 — Return the org-name constant.
// kOrgName is a std::string_view (a view onto the long-lived "..." literal).
// This function's return type is std::string, so we construct an owning string
// FROM the view: std::string { kOrgName }. That copy is intentional — the
// caller asked for its own header text. (A view -> string conversion must be
// explicit when narrowing ownership, which is exactly what the braces do.)
std::string badgeHeader()
{
return std::string { kOrgName };
}
// TASK 2 — "First Last".
// Seed an owning std::string from the first view, then append a space and the
// last view with +=. A std::string_view appends directly onto a std::string,
// so no manual character work (and no loop) is needed.
std::string fullName(std::string_view first, std::string_view last)
{
std::string result { first }; // owning copy of the first name
result += ' '; // single separating space
result += last; // append the last-name view
return result;
}
// TASK 3 — "Hello, my name is First Last".
// Compose: reuse fullName() instead of re-joining the two names by hand. Build
// the fixed prefix as a std::string, then append the assembled name.
std::string greeting(std::string_view first, std::string_view last)
{
std::string result { "Hello, my name is " };
result += fullName(first, last); // fullName returns a std::string; append it
return result;
}
// TASK 4 — "first<sep>last".
// Join with the NAMED separator kUsernameSep ('.') from badge.h — not a hard
// coded '.'. If the separator ever changes, this code does not.
std::string username(std::string_view first, std::string_view last)
{
std::string result { first };
result += kUsernameSep; // a char appends to a std::string with +=
result += last;
return result;
}
// TASK 5 — initials "F.L." with the empty-name guard.
// .front() on an EMPTY view is undefined behavior, so each contribution is
// guarded by an `if`. A non-empty name has .size() > 0 and adds
// "<first-char><sep>"; an empty one adds nothing.
// ("Ada","Lovelace") -> "A.L." ("","Lovelace") -> "L." ("","") -> ""
std::string initials(std::string_view first, std::string_view last)
{
std::string result {}; // start empty; build up what we can
if (first.size() > 0) // guard: never .front() an empty view
{
result += first.front(); // first character of the first name
result += kUsernameSep;
}
if (last.size() > 0)
{
result += last.front();
result += kUsernameSep;
}
return result;
}
// TASK 6 — length of the full name via .size().
// Reuse fullName(), then measure it. .size() returns std::size_t (UNSIGNED);
// the contract returns int, so convert explicitly with static_cast to make the
// signed/unsigned narrowing deliberate and warning-free.
int nameLength(std::string_view first, std::string_view last)
{
const std::string name { fullName(first, last) };
return static_cast<int>(name.size());
}
// ============================================================================
// Chapter 5 — Constants and Strings · Project: Name-Badge / Greeting Formatter
// tests/tests.cpp — the automated grader (tiny no-framework harness).
// ============================================================================
//
// Linked against starter/badge.cpp by `make test`, and against solution/badge.cpp
// by `make test-solution`. Each CHECK is one assertion; any failure prints the
// expression + line and the run ends non-zero (RED). All pass -> "PASS" (GREEN).
//
// Coverage: a normal case for every function, PLUS edge cases — empty names and
// single-character names — because .front() on an empty view is undefined
// behavior and the length/initials logic must hold at the boundaries.
#include <iostream>
#include <string>
#include "../badge.h"
static int fails = 0;
// Print the failing expression and source line; tally the failure.
#define CHECK(cond) do { \
if(!(cond)) { std::cerr << "FAIL: " #cond " @line " << __LINE__ << "\n"; ++fails; } \
} while(0)
int main()
{
// ── TASK 1: badgeHeader() returns the configured org name ───────────────
CHECK(badgeHeader() == "Cpp Foundations Lab");
CHECK(badgeHeader() == std::string { kOrgName }); // stays in sync with the constant
// ── TASK 2: fullName() -> "First Last" ──────────────────────────────────
CHECK(fullName("Ada", "Lovelace") == "Ada Lovelace");
CHECK(fullName("Grace", "Hopper") == "Grace Hopper");
CHECK(fullName("A", "B") == "A B"); // single-char edge case
// ── TASK 3: greeting() -> "Hello, my name is First Last" (reuses fullName)
CHECK(greeting("Ada", "Lovelace") == "Hello, my name is Ada Lovelace");
CHECK(greeting("Grace", "Hopper") == "Hello, my name is Grace Hopper");
// ── TASK 4: username() -> "first.last" using kUsernameSep ───────────────
CHECK(username("Ada", "Lovelace") == "Ada.Lovelace");
CHECK(username("Grace", "Hopper") == "Grace.Hopper");
// The separator must come from the named constant, not a hard-coded '.':
CHECK(username("x", "y") == std::string { "x" } + kUsernameSep + "y");
// ── TASK 5: initials() -> "F.L." with the empty-name guard ──────────────
CHECK(initials("Ada", "Lovelace") == "A.L.");
CHECK(initials("Grace", "Hopper") == "G.H.");
CHECK(initials("", "Lovelace") == "L."); // empty first name
CHECK(initials("Ada", "") == "A."); // empty last name
CHECK(initials("", "") == ""); // both empty -> no crash, no letters
CHECK(initials("a", "b") == "a.b."); // single-char names
// ── TASK 6: nameLength() -> length of "First Last" via .size() ──────────
CHECK(nameLength("Ada", "Lovelace") == 12); // "Ada Lovelace"
CHECK(nameLength("Grace", "Hopper") == 12); // "Grace Hopper"
CHECK(nameLength("A", "B") == 3); // "A B"
CHECK(nameLength("", "") == 1); // " " (just the joining space)
if (!fails) std::cout << "PASS ✅ all checks\n";
else std::cerr << "FAIL ❌ " << fails << " check(s) failed — fill in the TODO blocks.\n";
return fails ? 1 : 0;
}
// ============================================================================
// Chapter 5 — Constants and Strings · Project: Name-Badge / Greeting Formatter
// badge.h — the API you implement. DECLARATIONS ONLY (this file is complete).
// ============================================================================
//
// You fill in the function *bodies* in starter/badge.cpp. This header is the
// CONTRACT: it fixes each function's name, parameters, and return type. The
// grader (tests/tests.cpp) and the reference solution both #include this file,
// so do NOT change it — change starter/badge.cpp.
//
// Read the parameter and return TYPES carefully — they are the whole lesson:
//
// * Read-only inputs are std::string_view. A view is a cheap (pointer+length)
// READ-ONLY window onto characters that already live somewhere else. It does
// NOT own or copy them, so it is the right type for a parameter a function
// only reads. It happily accepts a C-style literal, a std::string, or
// another std::string_view — no copy required. (notes 5.8)
//
// * Functions that BUILD text return std::string (by value). A std::string
// OWNS its characters and can grow as you concatenate, so it is the right
// type for text you assemble and hand back. Returning it by value is normal
// and efficient in modern C++. (notes 5.7)
//
// * The org name is a constexpr std::string_view — a compile-time string
// symbolic constant. That is the Chapter 5 sweet spot: constexpr gives a
// named compile-time constant, string_view avoids owning/copying, and the
// "..." literal storage lives for the whole program. (notes 5.6, 5.8)
//
// SCOPE: const / constexpr, std::string, std::string_view, functions, and `if`.
// No loops (those are Chapter 8) — every task here is solved by BUILDING strings
// (concatenate with +, peek a character with .front(), measure with .size()),
// never by scanning character-by-character.
#ifndef BADGE_H // header guard: include this file safely many times
#define BADGE_H
#include <string> // std::string — owning, growable text
#include <string_view> // std::string_view — cheap read-only window
// ─── Compile-time configuration (named constants, not magic values) ─────────
// These are constexpr: their values are fixed and known AT COMPILE TIME, so the
// compiler can fold them straight into the generated code. Naming them is the
// whole point of notes 5.1 / 5.2 — a reader sees MEANING instead of a bare "."
// or "27" floating in the logic, and a future change happens in exactly one
// place. You will USE these in your function bodies instead of re-typing literals.
// (constexpr constants are safe to define in a header: each translation unit gets
// its own private copy. Chapter 7 later introduces `inline constexpr` for sharing
// ONE entity program-wide — not needed here.)
// The organization printed across the top of every badge.
constexpr std::string_view kOrgName { "Cpp Foundations Lab" };
// The character that joins a username, e.g. "ada.lovelace".
constexpr char kUsernameSep { '.' };
// The total width of a framed badge line. 27 columns is a layout decision, not
// a magic number — so it gets a name. None of the six required tasks need it; it
// is provided for the optional padBadge() stretch goal in the README.
constexpr int kBadgeWidth { 27 };
// ─── The API you implement (bodies live in starter/badge.cpp) ───────────────
// TASK 1 — Return the organization name (the constant kOrgName above).
// Note the conversion: kOrgName is a std::string_view but this returns a
// std::string. A view does NOT implicitly become an owning string — you make
// the copy explicit (std::string { kOrgName }) because the caller wants its
// own copy of the header text.
std::string badgeHeader();
// TASK 2 — Build a person's full name as "First Last" (one space between).
// Example: fullName("Ada", "Lovelace") -> "Ada Lovelace"
std::string fullName(std::string_view first, std::string_view last);
// TASK 3 — Build a friendly greeting that REUSES fullName():
// greeting("Ada", "Lovelace") -> "Hello, my name is Ada Lovelace"
std::string greeting(std::string_view first, std::string_view last);
// TASK 4 — Build a username "first<sep>last" using the kUsernameSep constant:
// username("Ada", "Lovelace") -> "Ada.Lovelace"
// (No case-changing — that needs a loop, which is Chapter 8. Just join them.)
std::string username(std::string_view first, std::string_view last);
// TASK 5 — Build initials from the FIRST character of each name, each followed
// by kUsernameSep: initials("Ada", "Lovelace") -> "A.L."
// EDGE CASE: calling .front() on an EMPTY view is undefined behavior. If a name
// is empty (.size() == 0), contribute no letter for it (but still no crash).
// initials("", "Lovelace") -> "L."
// initials("Ada", "") -> "A."
// initials("", "") -> ""
std::string initials(std::string_view first, std::string_view last);
// TASK 6 — Report the length of the full name as a plain int, using .size().
// .size() returns an UNSIGNED type (std::size_t); convert it to int for the
// return. Example: nameLength("Ada", "Lovelace") -> 12 ("Ada Lovelace" is 12).
int nameLength(std::string_view first, std::string_view last);
#endif // BADGE_H
# Chapter 5 — Name-Badge / Greeting Formatter · unit-test grader (Style B).
# Targets follow the drills/CLAUDE.md Makefile contract. TABS, not spaces.
#
# The learner implements function bodies in starter/badge.cpp against the API in
# badge.h. tests/tests.cpp links against EITHER starter or solution:
# make / make build compile the starter library (proves it builds)
# make test grade the STARTER -> RED until the TODOs are filled in
# make test-solution grade the SOLUTION -> MUST be GREEN (proof it's solvable)
CXX := clang++
CXXFLAGS := -std=c++17 -Wall -Wextra
.PHONY: all build run test solution test-solution clean
all: build
# `make build` — compile the starter library on its own (syntax/warning check).
# There is no main() here (this is a function library), so we compile to an
# object file rather than an executable.
build: starter/badge.o
starter/badge.o: starter/badge.cpp badge.h
$(CXX) $(CXXFLAGS) -c starter/badge.cpp -o starter/badge.o
# `make run` — exercise the starter's functions by running the grader against
# them (a library has no program of its own to run).
run: test
# `make test` — grade the LEARNER's code: link the tests against starter/badge.cpp.
test: tests/tests.cpp starter/badge.cpp badge.h
@$(CXX) $(CXXFLAGS) tests/tests.cpp starter/badge.cpp -o tests/run && ./tests/run
# `make solution` — build + run the grader against the reference solution.
solution: test-solution
# `make test-solution` — grade the SOLUTION: must be green (our proof it solves).
test-solution: tests/tests.cpp solution/badge.cpp badge.h
@$(CXX) $(CXXFLAGS) tests/tests.cpp solution/badge.cpp -o tests/run && ./tests/run
clean:
rm -f starter/badge.o solution/badge.o tests/run
make test locally
(see “Build & run locally” above).