Skip to content

Support zero-member types in NLOHMANN_DEFINE_TYPE_* macros (#4041)#5272

Open
nlohmann wants to merge 3 commits into
developfrom
claude/todo-49-fix-bd32cc
Open

Support zero-member types in NLOHMANN_DEFINE_TYPE_* macros (#4041)#5272
nlohmann wants to merge 3 commits into
developfrom
claude/todo-49-fix-bd32cc

Conversation

@nlohmann

Copy link
Copy Markdown
Owner

Summary

Fixes #4041: NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type) and its 11 sibling macros (_WITH_DEFAULT, _ONLY_SERIALIZE, NON_INTRUSIVE equivalents, and the DERIVED_TYPE family) produced broken generated code for types with no member variables to serialize.

Three prior PRs attempted this (#5142, #5099, #4994); all are stale and fail CI. This is a fresh implementation, not based on any of them:

  • Fix #4041: Support zero-member types in NLOHMANN_DEFINE_TYPE_* macros #5142's __VA_OPT__ approach breaks every pre-C++20 build (error C3861: '__VA_OPT__': identifier not found on MSVC's C++11 test project) because it isn't gated behind a C++20 check.
  • More fundamentally: invoking a variadic macro so that __VA_ARGS__ is empty is only standard-conforming since C++20 — GCC/Clang reject it under -pedantic pre-C++20 regardless of what the macro body does (verified directly against real GCC 16 / Clang with this repo's actual -pedantic -Werror -Wvariadic-macros flags). So a __VA_OPT__-only fix can, at best, only ever cover C++20+.
  • Fix NLOHMANN_DEFINE_TYPE_* macros for zero-member types #4994's ##__VA_ARGS__ GNU-extension attempt doesn't avoid this either (also verified directly) and only special-cases GCC.

Instead, this PR makes all 12 macros purely variadic (no fixed leading parameter) and dispatches on total argument count — 1 vs 2+ member arguments, or 2 vs 3+ for the DERIVED_TYPE macros whose fixed prefix is Type, BaseType — using a sentinel-padded extension of the existing NLOHMANN_JSON_GET_MACRO positional-dispatch idiom already used by NLOHMANN_JSON_PASTE1..64 in this file. This gives full C++11 through C++26 support with no feature-test gate at all.

WITH_NAMES variants are intentionally out of scope, matching issue #4041 and all three prior PRs.

Changes

  • include/nlohmann/detail/macro_scope.hpp: new NLOHMANN_JSON_CAT/NLOHMANN_JSON_TYPE_TAG/NLOHMANN_JSON_DERIVED_TYPE_TAG dispatch helpers; each of the 12 macros renamed to an _N/_2 (has-members) body plus a new _1/_2 (zero-member) body, with the public macro name becoming a 1-line dispatcher.
  • single_include/nlohmann/json.hpp: re-amalgamated (make amalgamate, astyle 3.4.13).
  • tests/src/unit-udt_macro.cpp: 12 new zero-member test cases (all 6 base macro variants + all 6 derived variants, the latter using a real base class to confirm base-class delegation still works with zero own members).
  • docs/mkdocs/docs/features/arbitrary_types.md: new "Zero-member types" note with an example.

Test plan

  • Existing unit-udt_macro.cpp suite (46 cases) passes unmodified — zero regressions from the macro rename/dispatch refactor.
  • New zero-member cases (12 sections, both nlohmann::json and nlohmann::ordered_json) pass.
  • Verified on real g++-16 and clang++ at -std=c++11/14/17/20 with -pedantic -Werror -Wvariadic-macros (matching cmake/gcc_flags.cmake).
  • make check-amalgamation clean.
  • Spot-checked an unrelated test file (unit-udt.cpp) still compiles/passes to rule out macro-name collisions from the new helpers.

This PR description was written by Claude Code.

NLOHMANN_DEFINE_TYPE_INTRUSIVE(Type) and its 11 sibling macros produced
broken code for types with no members to serialize. Invoking a variadic
macro so __VA_ARGS__ is empty is only standard-conforming since C++20,
so a plain __VA_OPT__ fix (as tried in #5142) breaks every pre-C++20
build under -pedantic. Instead, make all 12 macros purely variadic and
dispatch on argument count using a sentinel-padded extension of the
existing NLOHMANN_JSON_GET_MACRO idiom, giving full C++11-C++26 support
with no feature-test gate.

Verified against real GCC 16 and Clang at -std=c++11/14/17/20 with
-pedantic -Werror -Wvariadic-macros: zero regressions in the existing
unit-udt_macro.cpp suite plus 12 new zero-member test cases.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann nlohmann marked this pull request as draft July 11, 2026 21:51
nlohmann added 2 commits July 11, 2026 23:55
Three issues surfaced on PR #5272's real CI that weren't caught by
local testing against a narrower flag set:

- GCC -Werror=noexcept: the four truly-empty from_json bodies (plain
  INTRUSIVE/NON_INTRUSIVE, with and without _WITH_DEFAULT) provably
  never throw but weren't declared noexcept; mark them noexcept
  explicitly. to_json and the derived-type from_json overloads are
  left alone since they genuinely can throw (object assignment /
  delegating to the base class's from_json).
- clang-tidy bugprone-macro-parentheses: false positive on the same
  8 zero-member bodies (Type/BaseType used purely as declarator
  types); suppressed with NOLINTNEXTLINE comments in the same style
  already used elsewhere in this file (see NLOHMANN_JSON_SERIALIZE_ENUM).
- MSVC's traditional preprocessor doesn't fully expand
  NLOHMANN_JSON_CAT(prefix, NLOHMANN_JSON_TYPE_TAG(...))(...) in one
  pass, which broke a pre-existing one-member usage in
  unit-regression2.cpp with syntax errors. Wrap all 12 public
  dispatcher macros in an extra outer NLOHMANN_JSON_EXPAND(...),
  matching the pattern NLOHMANN_JSON_PASTE already uses for the same
  MSVC quirk.

Re-verified against real GCC 16 and Clang at -std=c++11/14/17/20 with
-pedantic -Werror -Wvariadic-macros -Wnoexcept, including the exact
files that failed in CI (unit-udt_macro.cpp, unit-regression2.cpp),
against both the modular headers and the re-amalgamated single header.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
The four zero-member ONLY_SERIALIZE test objects are only ever read
(via to_json), never mutated, so mark them const per clang-tidy.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann nlohmann force-pushed the claude/todo-49-fix-bd32cc branch from 3f8c58a to 4dcd45b Compare July 12, 2026 06:38
@nlohmann nlohmann marked this pull request as ready for review July 12, 2026 11:47
@nlohmann nlohmann added the review needed It would be great if someone could review the proposed changes. label Jul 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation L review needed It would be great if someone could review the proposed changes. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

NLOHMANN_DEFINE_TYPE_* fails with zero members

1 participant