Design Notes

Why automate documentation?

C++ API design is hard, and the moment you write a function signature is usually the moment you know the most about what it is supposed to do. Wait a week, and the details start to fade. Wait a month, and you reconstruct them from the call sites. Writing the documentation now, next to the declaration, captures what you already have in your head.

Keeping the docs next to the code does two things at once:

  • It makes them easier to update when a signature changes (you see them in the same diff), and

  • It makes them more likely to be written in the first place (no separate file to open).

Header-only handwritten reference documentation works for small libraries. They stop scaling as soon as the codebase outgrows what one developer can hold in memory, and they drift the moment a signature changes without an edit to the matching prose.

Generating the reference from the headers fixes the drift, but only if the generator reads those headers as the compiler does. Other tools often cannot do that, and the workaround is to keep a parallel ill-formed declaration just for the documentation tool. That defeats the point: the source code no longer reflects what is compiled.

Mr.Docs Other Tools Manual No Reference

Reader-friendly output

Yes

Yes

Yes

No

Stays in sync with source

Yes

Yes

No

No

No source-code workarounds

Yes

No

Yes

No

Low author overhead

Yes

No

No

Yes

Single source of truth

Yes

No

No

No

In other words, Mr.Docs gives users a presentable reference section without extra work for developers. It aims to address the reasons developers had to resort to workarounds in their code, so the documented C++ code remains the compiled C++ code.

Customization

Extraction turns the headers into a corpus. What readers then see depends on which generator runs over that corpus and how its templates and helpers are wired up. Mr.Docs offers three ways to customize that output, from least to most involved:

  • Configure an existing generator. The built-in generators (HTML, AsciiDoc, XML) accept template overrides and helper functions, so you change the look without writing C++. Most projects stop here.

  • Write a data-driven generator. Drop a directory of Handlebars templates into an addon, and Mr.Docs registers a new output format.

  • Link mrdocs-core and write a custom Generator subclass. Use this when you need to compare two corpora, run multi-pass analysis, or emit a binary format.

Mr.Docs deliberately does not generate the prose around the reference: overviews, tutorials, design notes like this one. Write those as ordinary AsciiDoc or Markdown next to the generated pages. The Antora extensions wire the two together so the hand-written prose can link into the generated reference with a cpp: macro.

Special C++ constructs

Mr.Docs parses C++ with Clang’s libtooling API, so anything that compiles is in the corpus, including templates, concepts, deduced return types, partial specializations, and coroutines. Getting the construct into the corpus is the easy part; rendering it the way a reader expects is where the options and metadata commands below come in.

Implementation details

Private and helper code often has to live next to the public API in C++ headers, and choosing what to expose is an API design decision. Filters decide what enters the corpus at all: include-symbols keeps a namespace, exclude-symbols drops sub-trees inside it. implementation-defined drops a page entirely and renders every reference to the symbol as /* implementation-defined */. see-below is the gentler form: the page stays but the synopsis collapses to /* see-below */. The same effect is available per-symbol from the doc comment with @implementationdefined and @seebelow.

Overload sets

A C++ overload set is many functions sharing a name. The default rendering is one page per signature; overloads merges them into a single page so the reader sees the whole set at once. Mr.Docs picks a canonical signature for the page title and lists each overload underneath with its own parameter table.

SFINAE constraints and concepts

A function constrained with enable_if, a requires clause, or a concept on its return type has a different signature from its unconstrained sibling. Readers care about the constraint, not about the spelling of typename std::enable_if<…​>::type. The sfinae option lifts the constraint into a "Requires" section attached to the signature and renders the return type as if the constraint were not there. Concepts and requires clauses go through the same path, so each constraint is documented as a named requirement rather than as type-system machinery.

Algorithm function objects

An Algorithm Function Object is a constexpr variable of an unnamed type with a single operator(). The variable is what the caller invokes; the type is an implementation detail. auto-function-objects detects this pattern and pulls the synopsis from the type’s operator(). When the type has extra members that defeat auto-detection, @functionobject on the variable forces the same treatment.

Inferred metadata

The first sentence of a doc comment is the symbol’s brief by default. auto-relates infers @relates from the first parameter type of a free function. auto-function-metadata lifts a @param, @returns, or @throws written on a base-class virtual into its overrides. These are opt-in shortcuts; everything they infer can also be written explicitly with the matching metadata command.

Inherited members

Multiple inheritance, EBO, and helper-base patterns produce members that are inherited but read as part of the derived class’s interface. Mr.Docs lists inherited members on the derived class’s page so readers do not have to chase the inheritance chain. inherit-base-members picks how, and distinguishes ordinary inheritance from helper-base patterns like CRTP and EBO, so the latter are not presented as user-facing members.

Resolved types and aliases

A few constructs are visible to Mr.Docs only because libtooling drives the front-end. Deduced return types (auto, decltype(auto)) are resolved against their definitions, so a function declared auto can show what it returns. Type aliases (typedefs, using declarations, namespace aliases) carry their target through the corpus, so a function declared in terms of an alias resolves to the underlying symbol when readers click through. Concepts get their own pages with the requires body intact.