From feed1a48a441513c5e8bd44da3b50a0e1a61b74f Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Fri, 28 Aug 2026 14:50:30 +0000 Subject: [PATCH] Only compile the simdutf backend from C++17 on simdutf.h rejects anything below C++17 with an #error, so defining JSON_USE_SIMDUTF in a C++11 or C++14 translation unit did not fail with a message about simdutf being unavailable - it failed to compile at all, taking the library's C++11 support with it. Nothing caught this because no build ever compiled that path. Gate the include and both uses on JSON_HAS_CPP_17, the same way number_parse.hpp gates std::from_chars. Below C++17 the macro now has no effect and the scalar validator runs; it accepts and rejects exactly the same input, so the macro is safe to set project-wide even when some translation units use an older standard. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01MXDi7NTMKAmoArUZSKMc4T Signed-off-by: Niels Lohmann --- .../docs/api/macros/json_use_simdutf.md | 7 +++++ include/nlohmann/detail/input/string_scan.hpp | 26 ++++++++++------- single_include/nlohmann/json.hpp | 28 +++++++++++-------- 3 files changed, 40 insertions(+), 21 deletions(-) diff --git a/docs/mkdocs/docs/api/macros/json_use_simdutf.md b/docs/mkdocs/docs/api/macros/json_use_simdutf.md index 507a1009b..26c54225d 100644 --- a/docs/mkdocs/docs/api/macros/json_use_simdutf.md +++ b/docs/mkdocs/docs/api/macros/json_use_simdutf.md @@ -18,6 +18,13 @@ scalar path. When `JSON_USE_SIMDUTF` is defined you must make the `simdutf.h` header available on the include path and link the simdutf library. When it is not defined, no simdutf header is included and there is no dependency. +!!! note "Requires C++17" + + simdutf requires C++17 and its header rejects older standards with an `#!cpp #error`. The backend is therefore only + compiled in from C++17 on. In C++11 and C++14 the macro has no effect and the scalar validator is used, which + accepts and rejects exactly the same input -- only throughput differs. Setting the macro project-wide is therefore + safe even when some translation units are built with an older standard. + !!! warning "Define consistently" The macro selects between two definitions of the same inline validation function. It must therefore be defined diff --git a/include/nlohmann/detail/input/string_scan.hpp b/include/nlohmann/detail/input/string_scan.hpp index 63c74db85..dc5b07a54 100644 --- a/include/nlohmann/detail/input/string_scan.hpp +++ b/include/nlohmann/detail/input/string_scan.hpp @@ -12,17 +12,23 @@ #include // uint64_t #include // memcpy -#if defined(JSON_USE_SIMDUTF) - // Optional SIMD backend for bulk UTF-8 validation. This is an opt-in - // external dependency: nlohmann/json itself stays header-only and the C++11 - // scalar validator below is always available; defining JSON_USE_SIMDUTF - // additionally requires the simdutf headers on the include path and linking - // the simdutf library. See string_bulk_run(). +#include + +// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external +// dependency: nlohmann/json itself stays header-only and the C++11 scalar +// validator below is always available; defining JSON_USE_SIMDUTF additionally +// requires the simdutf headers on the include path and linking the simdutf +// library. See string_bulk_run(). +// +// simdutf.h itself requires C++17 - it rejects older standards with an #error - +// so the backend is only compiled in from C++17 on. Below that the macro has no +// effect and the scalar validator is used; it accepts and rejects exactly the +// same input, so only throughput differs. macro_scope.hpp is included above to +// have JSON_HAS_CPP_17 available for this test. +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) #include #endif -#include - // This file contains the byte-level string-scanning helpers used by the lexer's // contiguous fast path. They operate purely on raw bytes (no dependency on the // lexer's template parameters) so they are free functions, keeping the lexer @@ -174,7 +180,7 @@ inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t return pos; } -#if defined(JSON_USE_SIMDUTF) +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) // Index of the first quote/escape/control byte in [data, data+n) (non-ASCII // bytes are *not* stops here - the whole run is handed to simdutf), or n. inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t n) noexcept @@ -221,7 +227,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t // produces the precise diagnostic. Without it, the pure scalar path is used. inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept { -#if defined(JSON_USE_SIMDUTF) +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) const std::size_t run = find_string_delimiter(data, n); if (run != 0 && simdutf::validate_utf8(reinterpret_cast(data), run)) { diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index f5887ad73..ef4f9da56 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -8166,18 +8166,24 @@ NLOHMANN_JSON_NAMESPACE_END #include // uint64_t #include // memcpy -#if defined(JSON_USE_SIMDUTF) - // Optional SIMD backend for bulk UTF-8 validation. This is an opt-in - // external dependency: nlohmann/json itself stays header-only and the C++11 - // scalar validator below is always available; defining JSON_USE_SIMDUTF - // additionally requires the simdutf headers on the include path and linking - // the simdutf library. See string_bulk_run(). - #include -#endif - // #include +// Optional SIMD backend for bulk UTF-8 validation. This is an opt-in external +// dependency: nlohmann/json itself stays header-only and the C++11 scalar +// validator below is always available; defining JSON_USE_SIMDUTF additionally +// requires the simdutf headers on the include path and linking the simdutf +// library. See string_bulk_run(). +// +// simdutf.h itself requires C++17 - it rejects older standards with an #error - +// so the backend is only compiled in from C++17 on. Below that the macro has no +// effect and the scalar validator is used; it accepts and rejects exactly the +// same input, so only throughput differs. macro_scope.hpp is included above to +// have JSON_HAS_CPP_17 available for this test. +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) + #include +#endif + // This file contains the byte-level string-scanning helpers used by the lexer's // contiguous fast path. They operate purely on raw bytes (no dependency on the // lexer's template parameters) so they are free functions, keeping the lexer @@ -8329,7 +8335,7 @@ inline std::size_t scalar_string_bulk_run(const unsigned char* data, std::size_t return pos; } -#if defined(JSON_USE_SIMDUTF) +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) // Index of the first quote/escape/control byte in [data, data+n) (non-ASCII // bytes are *not* stops here - the whole run is handed to simdutf), or n. inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t n) noexcept @@ -8376,7 +8382,7 @@ inline std::size_t find_string_delimiter(const unsigned char* data, std::size_t // produces the precise diagnostic. Without it, the pure scalar path is used. inline std::size_t string_bulk_run(const unsigned char* data, std::size_t n) noexcept { -#if defined(JSON_USE_SIMDUTF) +#if defined(JSON_USE_SIMDUTF) && defined(JSON_HAS_CPP_17) const std::size_t run = find_string_delimiter(data, n); if (run != 0 && simdutf::validate_utf8(reinterpret_cast(data), run)) {