Skip to content

Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907)#5248

Merged
nlohmann merged 3 commits into
developfrom
claude/todo-92-review-22e82b
Jul 9, 2026
Merged

Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907)#5248
nlohmann merged 3 commits into
developfrom
claude/todo-92-review-22e82b

Conversation

@nlohmann

@nlohmann nlohmann commented Jul 8, 2026

Copy link
Copy Markdown
Owner

What and why

#3907 reports that nlohmann/json.hpp fails to compile under nvcc at -std=c++20
(error: expected initializer before '<' token in iteration_proxy.hpp's
enable_borrowed_range specialization), but compiles fine at C++17. The issue was
previously closed as "un-reproducible in CI": the existing ci_cuda_example job built
against the json-ci image's CUDA 11.0 toolkit at cuda_std_11, and CUDA 11 has no
C++20 support at all, so the job could never hit this code path. A separate job
(ci_nvhpc) and a previously closed PR (#4796) both use CUDA 12.9, a version where the
bug is already fixed — so neither of those could catch it either.

This PR was done in two stages, both on this branch:

1. Diagnose first. Turned ci_cuda_example into a matrix pulling official
nvidia/cuda:*-devel-ubuntu22.04 images directly (same pattern as ci_nvhpc) across 8
CUDA versions (11.8.0 through 12.6.3) at cuda_std_20, to empirically confirm which
versions are actually affected instead of trusting an unverified issue-thread comment.
Result, reproduced against real nvcc:

CUDA (nvcc) Result
11.8.0 fails at CMake configure — no C++20 support in CUDA 11, unrelated to #3907
12.0.1 fails with the exact #3907 error
12.1.1 fails with the exact #3907 error
12.2.2 – 12.6.3 all pass

So the affected range is exactly nvcc 12.0.x/12.1.x, fixed by NVIDIA from 12.2 onward —
nothing broader or narrower.

2. Fix it. Added a targeted guard to the JSON_HAS_RANGES detection chain in
include/nlohmann/detail/macro_scope.hpp, disabling ranges support specifically for
nvcc 12.0/12.1 (detected via __CUDACC_VER_MAJOR__/__CUDACC_VER_MINOR__), matching the
style of the neighboring GCC-11/libstdc++ carve-outs already in that file. This is the
part that protects real client code — any project compiling this header with nvcc
12.0/12.1 at C++20 gets JSON_HAS_RANGES disabled automatically, regardless of their own
build system. Regenerated single_include/nlohmann/json.hpp accordingly.

Also broadened tests/cuda_example/json_cuda.cu (previously only called dump()/
erase()) to exercise operator==/operator<=> (three-way comparison, gated
independently by JSON_HAS_THREE_WAY_COMPARISON) and range-based iteration, so the CI
matrix gives real evidence about the fix's scope rather than a guess. This did catch a
real issue: once 11.8.0 started building at C++17 (see below), its use of <=>
unconditionally broke, since that operator isn't valid pre-C++20 syntax — fixed by
gating those two lines behind JSON_HAS_THREE_WAY_COMPARISON, the same way the library
itself does internally.

tests/cuda_example/CMakeLists.txt no longer hard-requires cuda_std_20 (which failed
outright on anything older than CUDA 12.0). It now picks the newest C++ standard the
detected nvcc version actually supports — 20 for CUDA ≥ 12.0, 17 for CUDA ≥ 11.0, else
11 — so older toolkits still build the smoke test at whatever standard they support
instead of erroring. This is local to our own test project only (a header can't control
its own -std= invocation); the actual protection for downstream users is the
macro_scope.hpp guard above.

With the fix in place, every CUDA version can build successfully at its own standard, so
the matrix no longer needs the full 8-version diagnostic sweep to avoid a permanently red
leg. Right-sized ci_cuda_example down to 3 legs, each testing something distinct:

Also updated the CUDA row in docs/mkdocs/docs/community/quality_assurance.md's compiler
table to match (previously listed a stale CUDA 11.0.221 / Ubuntu 20.04 entry).

Final CI on this branch: all three ci_cuda_example legs (11.8.0, 12.1.1, 12.6.3)
pass.

Changes

  • include/nlohmann/detail/macro_scope.hpp / single_include/nlohmann/json.hpp: nvcc
    12.0/12.1 JSON_HAS_RANGES guard (the actual fix for error: expected initializer before ‘<’ token  #3907).
  • tests/cuda_example/json_cuda.cu: broadened smoke test (comparisons + range-based
    iteration), <=> usage gated by JSON_HAS_THREE_WAY_COMPARISON.
  • tests/cuda_example/CMakeLists.txt: graceful per-nvcc-version C++ standard selection
    instead of hard-requiring cuda_std_20.
  • cmake/ci.cmake: dropped the stale -DCMAKE_CUDA_HOST_COMPILER=g++-8 pin on the
    ci_cuda_example target so it falls back to the stock host compiler in each image.
  • .github/workflows/ubuntu.yml: ci_cuda_example now matrices over
    nvidia/cuda:{11.8.0,12.1.1,12.6.3}-devel-ubuntu22.04 directly instead of the pinned
    ghcr.io/nlohmann/json-ci image, with a lukka/get-cmake step added.
  • docs/mkdocs/docs/community/quality_assurance.md: updated the CUDA compiler-version
    table row(s) to match.

The ci_cuda_example job compiled against the json-ci image's CUDA
11.0 toolkit at cuda_std_11, which cannot exercise #3907 (a c++20
parse error in iteration_proxy.hpp's enable_borrowed_range reported
under nvcc). Switch the job to pull official nvidia/cuda devel images
directly and matrix across CUDA 11.8-12.6 at cuda_std_20 so CI can
empirically confirm which versions are actually affected before any
source-level fix is attempted.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann marked this pull request as draft July 8, 2026 19:01
The diagnostic matrix in this PR confirmed the affected range exactly:
nvcc 12.0.1 and 12.1.1 both fail with "expected initializer before
'<' token" on iteration_proxy.hpp's enable_borrowed_range variable
template specialization at -std=c++20; 12.2.2 and newer already build
cleanly. Guard JSON_HAS_RANGES off for that narrow nvcc version range,
matching the existing GCC-11/libstdc++ carve-outs in the same ifdef
chain, and regenerate single_include accordingly.

Broaden the CUDA smoke test to also exercise comparisons
(operator==/operator<=>, gated independently by
JSON_HAS_THREE_WAY_COMPARISON) and range-based iteration, not just
dump()/erase(), so the fix's actual scope is evidenced by CI rather
than assumed from the single reported symptom.

Have tests/cuda_example/CMakeLists.txt pick the newest C++ standard
the detected nvcc version actually supports (20/17/11) instead of
hard-requiring C++20, so older toolkits build at a lower standard
instead of failing CMake configure outright. This is test-project-local
only; the JSON_HAS_RANGES guard is what protects real client code,
since a header can't control what -std= flag it's compiled with.

Right-size the CI matrix from the 8-version diagnostic sweep down to
11.8.0 (C++17 fallback path) / 12.1.1 (permanent #3907 regression
guard) / 12.6.3 (recent coverage), and update the compiler-version
table in the quality assurance docs to match.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
The 11.8.0 leg's graceful C++17 fallback (added in the previous commit)
worked correctly, but the broadened smoke test used the <=> operator
unconditionally, which isn't valid syntax pre-C++20 — nvcc rejected it
with "expected an expression" once the CMake logic picked cuda_std_17
for the older toolkit. Gate those two lines behind
JSON_HAS_THREE_WAY_COMPARISON like the library itself does internally.

Sanity-compiled the file as plain C++ at both -std=c++17 (skips the
guarded block) and -std=c++20 (includes it) locally; the actual nvcc
build is verified via CI on PR #5248.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
@nlohmann
nlohmann marked this pull request as ready for review July 9, 2026 11:45
@nlohmann nlohmann added this to the Release 3.13.0 milestone Jul 9, 2026
@nlohmann nlohmann changed the title Matrix ci_cuda_example across CUDA 11.8-12.6 at C++20 to reproduce #3907 Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907) Jul 9, 2026
@nlohmann nlohmann linked an issue Jul 9, 2026 that may be closed by this pull request
2 tasks
@nlohmann
nlohmann merged commit f8e99e8 into develop Jul 9, 2026
155 of 156 checks passed
@nlohmann
nlohmann deleted the claude/todo-92-review-22e82b branch July 9, 2026 17:02
nlohmann added a commit that referenced this pull request Jul 9, 2026
PR #5248 added a 5th JSON_HAS_RANGES exclusion branch to
macro_scope.hpp (nvcc CUDA 12.0.x/12.1.x, fixed in 12.2, issue #3907)
shortly after #5252 added the "Known compiler/stdlib exclusions"
list to json_has_ranges.md, so the new branch was missing from the
just-added doc section. Bring the list back to parity with the code
(5 exclusion branches, 5 documented).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

error: expected initializer before ‘<’ token

1 participant