Skip to content

Contributing to cpp-grail

Repository shape

hundred/NN-โ€ฆ/     The Hundred โ€” curated, difficulty-ordered, CLOSED
gotchas/<slug>/   open collection: traps that bite real code
trivia/<slug>/    open collection: legal, astonishing obscura
templates/        skeletons to copy for new entries
tools/            validators, index generation, and website staging
website/          themes, the quiz page, site CSS and JavaScript

Each entry is a directory holding README.md and main.cpp โ€” nothing else. A few entries legitimately carry a second .cpp or an .hpp because the lesson needs two translation units.

The three collections

Collection Where Status
The Hundred hundred/NN-<slug>/ Frozen. Curated, difficulty-ordered, closed to new entries.
Gotchas gotchas/<slug>/ Open. Traps that bite real code. Same contract, no number.
Trivia trivia/<slug>/ Open. C++ obscura: true, strange, and mostly useless.

The Hundred is finished. Its numbers encode difficulty (๐ŸŸข 01โ€“29, ๐ŸŸก 30โ€“69, ๐Ÿ”ด 70โ€“100), which only works for a closed set โ€” inserting an entry would renumber its neighbors and break every link and cross-reference. So new entries never get a number. They live in gotchas/ or trivia/ under a slug, and the index orders them instead.

Corrections to an existing entry are always welcome (a wrong claim, a stale compiler message, a typo). Renumbering and reordering are not.

Which type is it?

Would you write a "The fix" section with a straight face? Yes โ†’ gotcha. No โ†’ trivia.

A gotcha is a trap. Someone writing ordinary code hits it, loses an afternoon, and there is a concrete thing they should have done instead.

Trivia is obscura. It is legal, it is astonishing, and there is usually nothing to fix because nobody writes this by accident. Trivia entries state their honest impact: โ€” none, rare, or real โ€” and must cite the standard, because obscura is exactly where confident wrong claims come from.

If a trivia entry turns out to bite people, it was a gotcha all along. Move it.

Adding an entry

The easiest path is to let a coding agent walk you through it. The procedure lives in the repo, so Claude Code, Codex and friends all follow the same steps:

Task Claude Code Any other agent
Add an entry /add-gotcha follow .claude/skills/add-gotcha/SKILL.md
Fix a wrong entry /report-correction follow .claude/skills/report-correction/SKILL.md
Add a theme /add-theme follow .claude/skills/add-theme/SKILL.md

The skill picks the right collection, checks your idea is not already covered, makes you verify the claim on a real compiler before writing a word of prose, and runs the checker and the index generator for you. AGENTS.md routes agents that read that convention instead.

Doing it by hand is fine too โ€” the skill is just this, written down:

cp -r templates/gotcha gotchas/my-slug     # or templates/trivia trivia/my-slug
$EDITOR gotchas/my-slug/main.cpp gotchas/my-slug/README.md

python3 tools/check.py                     # structure, build, run, documented output
python3 tools/gen_index.py                 # regenerates README.md
python3 tools/build_site.py                 # checks website, quiz data and CE links

Then open a PR. Never hand-edit the index tables in README.md โ€” they are generated, so editing them by hand guarantees a merge conflict with the next contributor.

Slugs are lowercase words separated by hyphens (erase-remove-idiom). They may not start with digits โ€” that shape is reserved for The Hundred.

What an entry contains

Two files, and nothing else โ€” no binaries, no scratch files, no core dumps (ulimit -c 0 for entries that crash on purpose).

  • main.cpp โ€” the demo. Self-contained, ideally under 35 lines, compiling cleanly under g++ -std=c++17 -Wall -Wextra main.cpp. Extra .cpp/.hpp files are allowed only when the lesson inherently needs multiple translation units.
  • README.md โ€” the write-up, under 70 lines, in the shape below.

README skeleton

# NN ยท Title          <!-- The Hundred; new entries drop the number -->

๐ŸŸข **Beginner** โ€” <one-line hook that hints at the surprise without spoiling it>

โ€‹```cpp
<the snippet โ€” same code as main.cpp, possibly trimmed of includes>
โ€‹```

**Run it. What does it print?**   <!-- adapt the question to the gotcha -->

<details>
<summary><b>Answer</b></summary>

<the surprising truth, in one or two short lines>

</details>

## Why

<the explanation โ€” at most 6 sentences, one idea only>

## The fix

โ€‹```cpp
<the corrected line(s)>
โ€‹```

**Takeaway:** <one sentence the reader can memorize>

---
*Try it: `g++ -std=c++17 main.cpp -o demo && ./demo`*

Trivia differs in exactly two places. ## The fix becomes ## Where it shows up โ€” there is usually nothing to fix, and pretending otherwise turns a curiosity into a fake warning; if the honest answer is "nowhere, nobody writes this on purpose", write that. And the standard must be cited, as a bracket-tag ([expr.sub]) or a cppreference link. Everything else is identical.

The bar

The first hundred entries were each compiled, run, and adversarially fact-checked before landing. New entries meet the same bar:

  1. It runs. Every claim about output came from actually running the program on a real compiler. Quote real output and real warning text โ€” never what you assume it prints. tools/check.py compiles the entry, runs it, and requires the answer block to quote exactly what it printed, so a snippet edited without re-running the demo fails.
  2. Undefined behavior is labeled. Say undefined behavior in bold, and present observed output as typical, never guaranteed. "It may even appear to work" is itself part of the lesson.
  3. The snippet looks innocent. No comment gives the surprise away. The reader should form a confident wrong prediction before scrolling.
  4. One idea. Related traps get one sentence and a pointer, not a section.
  5. It is short. README under 70 lines, main.cpp around 40 or fewer.
  6. It is not already here. Check the index. An entry that restates another entry's rule should instead link to it.
  7. Trivia cites the standard. A bracket-tag ([expr.sub]) or a cppreference link.

tools/check.py enforces items 1, 5 and the mechanical half of 7. Items 2, 3, 4 and 6 โ€” labelling undefined behavior, keeping the snippet innocent, one idea per entry, and not restating an entry that already exists โ€” are what review is for.

Two limits worth knowing. An entry that documents more than one build (the assert demo quotes both a normal and an -DNDEBUG run) has only its declared build: re-run, so the other block rests on review. And an answer that quotes no fenced output claims nothing for the checker to compare against.

Writing rules

  • Lead with code, not theory. The reader should form a wrong prediction first.
  • Mention helpful warnings. If -Wall -Wextra or a named flag catches the bug, say which one โ€” knowing what the compiler can catch is part of the lesson.
  • Compile-error entries keep main.cpp compiling by default. Guard the broken line with #ifdef SHOW_BUG, tell the reader to try g++ -DSHOW_BUG โ€ฆ, and quote only the interesting line or two of the error, never the wall of text.
  • The baseline is C++17. If the behavior changed in another standard, one sentence on what changed and when.
  • At most one external link (cppreference), and only when it genuinely adds value.
  • Match the neighbors. Follow the formatting the existing entries use rather than imposing your own; an editor-side formatter owns the source style, so do not reformat entries you are not otherwise changing.

STYLE.md in the repository carries the same contract in full.

Entry metadata

Every entry in gotchas/ and trivia/ starts its README with a metadata block. The tooling reads it; GitHub renders it invisible.

<!-- cpp-grail
type: gotcha              # gotcha | trivia
difficulty: intermediate  # gotchas only: beginner | intermediate | advanced
impact: none              # trivia only: none | rare | real
standard: [expr.sub]      # required for trivia, optional for gotchas
build: g++ -std=c++17 -Wall -Wextra main.cpp -o demo
expect: run               # run | crash | compile-error
showbug: error            # optional: a -DSHOW_BUG build must fail
-->

expect: is what lets a demo fail on purpose. crash means the program is supposed to die by a signal (a double free, a SIGFPE); compile-error means the default build is supposed to be rejected. Without it, the checker would call your best entries broken.

The website derives each Compiler Explorer link from main.cpp and the metadata build: flags. Keep that command as a direct g++ invocation with explicit source files and options. Multi-file lessons are supported on the site, but their Compiler Explorer button is deliberately labeled as a main.cpp-only view. See WEBSITE.md for local setup and the generated-content contract.

Verifying your work

python3 tools/check.py           # validate gotchas/ and trivia/, output included
python3 tools/check.py --all     # also smoke-build all 100
python3 tools/gen_index.py       # regenerate the index
python3 -m unittest discover -s tools/tests -v
python3 tools/build_site.py && mkdocs build --strict   # needs requirements-docs.txt

CI runs these, rejects a stale index, and builds the website before deployment. .site-src/ and .site-output/ are disposable generated directories; never edit or commit them.

Adding a theme

Entries are not the only thing you can contribute. The site's look is a set of interchangeable themes, and adding one is a single new file in website/themes/ โ€” no other file changes.

Run /add-theme and an agent will walk you through it, or follow .claude/skills/add-theme/SKILL.md by hand. The token contract, the rules the build enforces, and a copy-paste skeleton are in website/themes/README.md.

A theme sets color, type, borders and shadows โ€” never layout, so switching themes never moves the page. The build rejects a theme that tries.