From f8e99e856c3219480f7bb2e6fe6d7e37fb2068f4 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 9 Jul 2026 19:02:36 +0200 Subject: [PATCH] Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907) (#5248) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Test ci_cuda_example against a CUDA version matrix at C++20 (#3907) 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 * Fix nvcc CUDA 12.0/12.1 C++20 ranges parse error (#3907) 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 * Fix ci_cuda_example CUDA 11.8 build after C++17 fallback (#3907) 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 --------- Signed-off-by: Niels Lohmann --- .github/workflows/ubuntu.yml | 13 ++++++++++++- cmake/ci.cmake | 1 - docs/mkdocs/docs/community/quality_assurance.md | 4 +++- include/nlohmann/detail/macro_scope.hpp | 5 +++++ single_include/nlohmann/json.hpp | 5 +++++ tests/cuda_example/CMakeLists.txt | 13 ++++++++++++- tests/cuda_example/json_cuda.cu | 16 ++++++++++++++++ 7 files changed, 53 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 7e27ab332..5ccba335b 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -234,11 +234,22 @@ jobs: ci_cuda_example: runs-on: ubuntu-latest - container: ghcr.io/nlohmann/json-ci:v2.4.0 + strategy: + fail-fast: false + matrix: + # 11.8.0: newest pre-C++20 CUDA release, exercises the C++17 fallback + # path (tests/cuda_example/CMakeLists.txt picks the standard per nvcc + # version); 12.1.1: permanent regression guard for #3907 (nvcc 12.0/12.1 + # choke on enable_borrowed_range at C++20, fixed in 12.2); 12.6.3: recent + # CUDA/C++20 coverage. + cuda: ['11.8.0', '12.1.1', '12.6.3'] + container: nvidia/cuda:${{ matrix.cuda }}-devel-ubuntu22.04 steps: - uses: actions/checkout@9c091bb21b7c1c1d1991bb908d89e4e9dddfe3e0 # v7.0.0 with: persist-credentials: false + - name: Get latest CMake and ninja + uses: lukka/get-cmake@f5b8fbb4d77cec1acc5a5f9f0df4beffaf5d98d9 # v4.3.4 - name: Run CMake run: cmake -S . -B build -DJSON_CI=On - name: Build diff --git a/cmake/ci.cmake b/cmake/ci.cmake index bc7f09d75..9cc850564 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -669,7 +669,6 @@ add_custom_target(ci_test_compiler_default add_custom_target(ci_cuda_example COMMAND ${CMAKE_COMMAND} -DCMAKE_BUILD_TYPE=Debug -GNinja - -DCMAKE_CUDA_HOST_COMPILER=g++-8 -S${PROJECT_SOURCE_DIR}/tests/cuda_example -B${PROJECT_BINARY_DIR}/build_cuda_example COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_cuda_example ) diff --git a/docs/mkdocs/docs/community/quality_assurance.md b/docs/mkdocs/docs/community/quality_assurance.md index 1314e72a4..02ccccd9d 100644 --- a/docs/mkdocs/docs/community/quality_assurance.md +++ b/docs/mkdocs/docs/community/quality_assurance.md @@ -66,7 +66,9 @@ Note: Some modern features (like C++20 ranges or filesystem support) may be disa | Clang 20.1.1 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | | Clang 20.1.8 with GNU-like command-line | x86_64 | Windows Server 2022 (Build 20348) | GitHub | | Clang 21.1.8 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | - | CUDA 11.0.221 (nvcc) | x86_64 | Ubuntu 20.04 LTS | GitHub | + | CUDA 11.8.0 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | + | CUDA 12.1.1 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | + | CUDA 12.6.3 (nvcc) | x86_64 | Ubuntu 22.04 LTS | GitHub | | Emscripten 4.0.6 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | | GNU 4.8.5 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | | GNU 4.9.3 | x86_64 | Ubuntu 22.04.1 LTS | GitHub | diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 08c9fbc6c..4682fd361 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -146,6 +146,11 @@ #define JSON_HAS_RANGES 0 #elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000 #define JSON_HAS_RANGES 0 + // nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template + // syntax when compiling as CUDA source; fixed in CUDA 12.2 (issue #3907) + #elif defined(__CUDACC__) && defined(__CUDACC_VER_MAJOR__) && __CUDACC_VER_MAJOR__ == 12 \ + && defined(__CUDACC_VER_MINOR__) && (__CUDACC_VER_MINOR__ == 0 || __CUDACC_VER_MINOR__ == 1) + #define JSON_HAS_RANGES 0 #elif defined(__cpp_lib_ranges) #define JSON_HAS_RANGES 1 #else diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index a63e3326b..806b02ca9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2520,6 +2520,11 @@ JSON_HEDLEY_DIAGNOSTIC_POP #define JSON_HAS_RANGES 0 #elif defined(_LIBCPP_VERSION) && _LIBCPP_VERSION < 160000 #define JSON_HAS_RANGES 0 + // nvcc CUDA 12.0/12.1 chokes on the enable_borrowed_range variable-template + // syntax when compiling as CUDA source; fixed in CUDA 12.2 (issue #3907) + #elif defined(__CUDACC__) && defined(__CUDACC_VER_MAJOR__) && __CUDACC_VER_MAJOR__ == 12 \ + && defined(__CUDACC_VER_MINOR__) && (__CUDACC_VER_MINOR__ == 0 || __CUDACC_VER_MINOR__ == 1) + #define JSON_HAS_RANGES 0 #elif defined(__cpp_lib_ranges) #define JSON_HAS_RANGES 1 #else diff --git a/tests/cuda_example/CMakeLists.txt b/tests/cuda_example/CMakeLists.txt index 26b57f227..12f0fb4c2 100644 --- a/tests/cuda_example/CMakeLists.txt +++ b/tests/cuda_example/CMakeLists.txt @@ -3,7 +3,18 @@ project(json_cuda LANGUAGES CUDA) add_executable(json_cuda json_cuda.cu) target_include_directories(json_cuda PRIVATE ../../include) -target_compile_features(json_cuda PUBLIC cuda_std_11) + +# nvcc added C++20 support in CUDA 12.0 and C++17 in CUDA 11.0; pick the +# newest standard the detected compiler actually supports (see #3907) +# instead of hard-requiring one standard for every CUDA version. +if(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 12.0) + set(json_cuda_std 20) +elseif(CMAKE_CUDA_COMPILER_VERSION VERSION_GREATER_EQUAL 11.0) + set(json_cuda_std 17) +else() + set(json_cuda_std 11) +endif() +target_compile_features(json_cuda PUBLIC cuda_std_${json_cuda_std}) set_target_properties(json_cuda PROPERTIES CUDA_EXTENSIONS OFF CUDA_STANDARD_REQUIRED ON diff --git a/tests/cuda_example/json_cuda.cu b/tests/cuda_example/json_cuda.cu index a27378aec..76d631fa4 100644 --- a/tests/cuda_example/json_cuda.cu +++ b/tests/cuda_example/json_cuda.cu @@ -16,4 +16,20 @@ int main() // regression for #3013 (ordered_json::reset() compile error with nvcc) nlohmann::ordered_json metadata; metadata.erase("key"); + + // exercise comparisons (operator==/operator<=>, gated by + // JSON_HAS_THREE_WAY_COMPARISON, independent of JSON_HAS_RANGES) and + // range-based iteration (exercises iteration_proxy/ranges machinery + // beyond just the enable_borrowed_range specialization) — see #3907 + nlohmann::json a = {1, 2, 3}; + nlohmann::json b = {1, 2, 3}; + static_cast(a == b); +#if JSON_HAS_THREE_WAY_COMPARISON + static_cast(a <=> b); // *NOPAD* + static_cast(a <=> 1); // *NOPAD* +#endif + for (const auto& element : a) + { + static_cast(element); + } }