From c8e0f8a249d7e7919cd0e968fab604dfc1797ebe Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Thu, 20 Aug 2026 03:46:03 +0200 Subject: [PATCH] Gate the C++20 iterator classification on JSON_HAS_RANGES Making supports_bulk_scan depend on iterator_is_contiguous meant the trait is now instantiated for every adapter, not only when get_elements() is called. On standard libraries with an incomplete that is fatal: libstdc++ 10 evaluates std::contiguous_iterator> by calling std::to_address, which needs an operator-> its counted_iterator does not have, so satisfaction checking is a hard error rather than false. Reported by clang 14 + libstdc++ 10. JSON_HAS_RANGES already encodes exactly this ("libstdc++ < 11 has incomplete C++20 ranges", #4440), so require it for the C++20 branch. Affected toolchains fall back to the pointer-only test and the byte-at-a-time scanner, which parses identically, just without the bulk fast paths. Signed-off-by: Niels Lohmann --- include/nlohmann/detail/input/input_adapters.hpp | 13 ++++++++++--- tests/src/unit-user_defined_input.cpp | 5 +++++ 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index a3bea33fa..cc5554c9e 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -166,8 +166,15 @@ class iterator_input_adapter // std::distance) or, in C++20, the sentinel is a sized sentinel for the // iterator (std::ranges::distance), e.g. std::default_sentinel_t paired // with std::counted_iterator. + // + // JSON_HAS_RANGES gates the C++20 branch: on standard libraries with an + // incomplete (libstdc++ < 11, see #4440) evaluating + // std::contiguous_iterator on a std::counted_iterator is a hard error + // instead of yielding false, and these traits are instantiated for every + // adapter. Such toolchains fall back to the pointer-only test and simply + // use the byte-at-a-time scanner. static constexpr bool sentinel_is_sized = -#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) +#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) std::is_same::value || std::sized_sentinel_for; #else std::is_same::value; @@ -236,7 +243,7 @@ class iterator_input_adapter // available element count must also be computable in O(1), hence // sentinel_is_sized. static constexpr bool iterator_is_contiguous = sentinel_is_sized && -#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) +#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) (std::contiguous_iterator || std::is_pointer::value); #else std::is_pointer::value; @@ -245,7 +252,7 @@ class iterator_input_adapter // number of unread elements in [current, end) std::size_t remaining_count() const { -#if defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) +#if JSON_HAS_RANGES && defined(__cpp_lib_concepts) && defined(JSON_HAS_CPP_20) // std::ranges::distance also supports sized sentinels of a different // type (e.g. std::counted_iterator + std::default_sentinel_t) return static_cast(std::ranges::distance(current, end)); diff --git a/tests/src/unit-user_defined_input.cpp b/tests/src/unit-user_defined_input.cpp index 48083e3cc..5e57d0914 100644 --- a/tests/src/unit-user_defined_input.cpp +++ b/tests/src/unit-user_defined_input.cpp @@ -235,9 +235,14 @@ TEST_CASE("std::counted_iterator reaches the contiguous fast paths") // std::counted_iterator over a contiguous iterator must reach the same bulk // string/number scanners as a plain pointer - not just the byte-at-a-time // fallback (see #5268 for the equivalent memcpy fast path). +#if JSON_HAS_RANGES + // JSON_HAS_RANGES is 0 on standard libraries with an incomplete + // (libstdc++ < 11, libc++ < 16), where the adapter deliberately falls back + // to the byte-at-a-time scanner; everything below still has to work there. using adapter_type = nlohmann::detail::iterator_input_adapter, std::default_sentinel_t>; CHECK(adapter_type::supports_bulk_scan); CHECK(adapter_type::supports_seek); +#endif // exercise every fast path: long ASCII run, multibyte UTF-8, escapes, and // integer/floating-point numbers