Files
json/tools/macro_builder/README.md
T
Niels Lohmann 7aeb98414e Extend tools/macro_builder to also generate DOUBLE_PASTE and TYPE_BODY
tools/macro_builder only emitted NLOHMANN_JSON_EXPAND..PASTE64, so two
other tables that scale with the same max_args stayed hand-maintained
with nothing checking them: NLOHMANN_JSON_DOUBLE_PASTE (added by hand
in #4563 for the *_WITH_NAMES macros) and the 64-slot dispatch table of
NLOHMANN_JSON_TYPE_BODY (the #4041 zero-member/one-or-more-member
switch). Both tables pass one macro name per slot to the same
NLOHMANN_JSON_GET_MACRO dispatch as PASTE, so they can drift out of
sync with max_args exactly the way _dp did in the ABI tag list fixed
by #5544.

Extend main.cpp with build_double_paste_code() (same recursive-doubling
shape as build_paste_code(), but DOUBLE_PASTE consumes two arguments
per member, so an even slot index falls back to the next lower odd
DOUBLE_PASTE<N>) and build_type_body_table() (max_args - 1 MEMBERS
slots and one trailing EMPTY slot, 8 per line, matching how it is
written by hand today). Add a "type_body" argument that selects the
TYPE_BODY block, since it lives at a separate location in
macro_scope.hpp from the EXPAND..DOUBLE_PASTE63 block; plain invocation
is unchanged apart from covering the extended range. No longer emit
the tool's old trailing blank line, so its output is directly diffable
without post-processing.

Verified with c++ -std=c++11: running the tool (with and without
"type_body") and piping the raw output through the pinned astyle
reproduces both blocks of the current macro_scope.hpp byte for byte.
tests/src/unit-udt_macro.cpp (all NLOHMANN_DEFINE_TYPE_*/_WITH_NAMES/
zero-member variants) passes unchanged under -std=c++11 and -std=c++17
with -fsanitize=address,undefined.

Add a "macro_builder_check" Makefile target that builds main.cpp,
regenerates both blocks into a scratch directory inside the repository
(astyle's --project lookup needs the target files under the same tree
as .astylerc, unlike an external /tmp directory), and diffs them
against the corresponding ranges of macro_scope.hpp; wire it into
check-amalgamation next to the natvis check. Wire the same regeneration
into check_amalgamation.yml, splicing the (still unindented) generated
blocks back into the PR's own macro_scope.hpp before the existing
astyle/amalgamation step runs, so that step's own tree-wide astyle
pass both indents them and folds any drift into the amalgamation
patch/diff the workflow already produces.

Unlike amalgamate.py and generate_natvis.py, this step builds
tools/macro_builder/main.cpp from the pull request's own checkout
($MAIN_DIR) rather than a separate checkout of tools/ at develop: this
tool has no independent source of truth to regenerate against (its
README documents that it must reproduce macro_scope.hpp byte for
byte), so a develop-pinned copy would only reproduce the
generate_natvis.py trap fixed in a previous commit on this branch,
where a PR that teaches the tool to cover more of the file fails its
own CI until that PR merges and updates the develop copy.

Add tools/macro_builder/README.md documentation for both new tables
and the two-invocation usage, and a short pointer comment above
NLOHMANN_JSON_TYPE_BODY (the EXPAND pointer already covered the first
block; extended its wording to include DOUBLE_PASTE63).

Closes #5717 item 5 in full, completing what the documentation-only
"Document tools/macro_builder..." commit already on this branch left
open (that commit's README/pointer-comment half stands; it also covers
item 7).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 18:35:18 +02:00

2.7 KiB

macro_builder

Generates the argument-counting macros behind the NLOHMANN_DEFINE_TYPE_* and NLOHMANN_DEFINE_DERIVED_TYPE_* macros in include/nlohmann/detail/macro_scope.hpp:

  • NLOHMANN_JSON_EXPAND
  • NLOHMANN_JSON_GET_MACRO, which selects a macro by the number of its arguments (64 slots)
  • NLOHMANN_JSON_PASTE, which calls a function-like macro for each member, and its helpers NLOHMANN_JSON_PASTE2 to NLOHMANN_JSON_PASTE64
  • NLOHMANN_JSON_DOUBLE_PASTE, which the *_WITH_NAMES macros use to call a function-like macro for each (JSON name, member) pair, and its helpers NLOHMANN_JSON_DOUBLE_PASTE3 to NLOHMANN_JSON_DOUBLE_PASTE63
  • the slot table of NLOHMANN_JSON_TYPE_BODY, which dispatches NLOHMANN_DEFINE_TYPE_*(Type) (no further arguments) to the zero-member implementation and every other argument count to the one-or-more-member implementation

The number of slots (max_args in main.cpp) sets the member limit of these macros. NLOHMANN_JSON_PASTE and NLOHMANN_JSON_TYPE_BODY take the function/prefix as their first argument, so 64 slots allow 63 members; NLOHMANN_JSON_DOUBLE_PASTE additionally consumes its members two at a time (name, member), so it only defines the odd helpers up to NLOHMANN_JSON_DOUBLE_PASTE63.

Usage

From the project root:

c++ -std=c++11 tools/macro_builder/main.cpp -o macro_builder
./macro_builder
./macro_builder type_body
  1. Run ./macro_builder (no arguments). In include/nlohmann/detail/macro_scope.hpp, replace the lines from #define NLOHMANN_JSON_EXPAND( x ) x to the #define NLOHMANN_JSON_DOUBLE_PASTE63(...) line with the output, without its trailing empty line.
  2. Run ./macro_builder type_body. Replace the lines from #define NLOHMANN_JSON_TYPE_BODY(Prefix, ...) to the NLOHMANN_JSON_TYPE_BODY_SENTINEL)) line with the output.
  3. Run make amalgamate. It updates single_include/nlohmann/json.hpp and runs make pretty, which indents the continuation lines that the tool writes unindented.

With an unchanged main.cpp, these steps reproduce both blocks of macro_scope.hpp byte for byte. make macro_builder_check (also run by CI, see .github/workflows/check_amalgamation.yml) automates this: it builds main.cpp, regenerates both blocks, and fails on a diff against the checked-in header.

Maintained by hand

The tool does not generate everything that depends on the number of slots. When changing max_args, also update:

  • the documented limit of 63 members in docs/mkdocs/docs and the tests at that limit in tests/src/unit-udt_macro.cpp

All three tables pass one macro name per slot to NLOHMANN_JSON_GET_MACRO, so they need exactly as many entries as it has slots.