From a96d1b3a1e002a88960196e21a31d18c50162cc3 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 30 Sep 2026 18:43:30 +0200 Subject: [PATCH] Avoid std::basic_string for non-character output_adapter CharType output_adapter defaulted StringType to std::basic_string, and (with JSON_NO_IO undefined) always declared a std::basic_ostream&-taking constructor. For CharType with no non-deprecated std::char_traits specialization (only std::uint8_t is ever used this way, by the binary writers), simply naming either type - as an unused default template argument, or as an unused, never-called constructor's parameter type - instantiates std::char_traits merely to name it, which some standard libraries mark deprecated: with the library-wide -Wdocumentation pragma (item 2's other half, left for a later commit) temporarily removed, an Apple clang 21 / libc++ TU calling json::to_cbor(j, vec) with std::vector& got one -Wdeprecated-declarations warning per binary writer at the old output_adapters.hpp:193. Replaced the eager std::basic_string / std::basic_ostream defaults with a bool-tagged partial specialization (not std::conditional, which requires naming both branches' types up front regardless of which is selected, reproducing the same warning) that only ever names std::basic_string / std::basic_ostream when CharType is actually one of char, wchar_t, char16_t, char32_t, or (with __cpp_lib_char8_t) char8_t. For any other CharType, output_adapter's StringType and ostream-constructor parameter fall back to two distinct empty placeholder types, kept distinct so the two constructor overloads do not collide into a single redeclaration. Public API / behavior: passing a std::basic_string& or std::basic_ostream& directly to a binary writer's output_adapter now fails to compile instead of compiling with a deprecation warning; this was neither documented nor tested. All documented uses (std::vector, std::basic_ostream and StringType for character CharType) are unaffected. Verified with Apple clang 21 / libc++, with the two -Wdocumentation* "ignored" pragma lines in macro_scope.hpp temporarily removed and -std=c++11/c++20 plus the project's -Weverything flag set: calling to_cbor/to_msgpack/to_ubjson/to_bjdata/to_bson/to_bon8 on a std::vector now produces no char_traits (or any other) deprecation warning, while the char-based string- and ostream-adapter paths, and a to_cbor/from_cbor round trip, still compile and run correctly; also verified with GCC 16.2.0. Ran the full local test suite, including the binary-format unit tests (unit-cbor, unit-msgpack, unit-ubjson, unit-bjdata, unit-bson, unit-bon8, unit-binary_writer_sinks, unit-binary_formats, unit-custom-binary-type): 129/129 passing. #5725 item 2 (step a) Signed-off-by: Niels Lohmann --- .../detail/output/output_adapters.hpp | 80 ++++++++++++++++++- single_include/nlohmann/json.hpp | 80 ++++++++++++++++++- 2 files changed, 156 insertions(+), 4 deletions(-) diff --git a/include/nlohmann/detail/output/output_adapters.hpp b/include/nlohmann/detail/output/output_adapters.hpp index 679feca1c..75694b01b 100644 --- a/include/nlohmann/detail/output/output_adapters.hpp +++ b/include/nlohmann/detail/output/output_adapters.hpp @@ -13,6 +13,7 @@ #include // back_inserter #include // shared_ptr, make_shared #include // basic_string +#include // conditional, integral_constant, is_same #include // move #include // vector @@ -192,7 +193,82 @@ class output_adapter_sink output_adapter_t oa; }; -template> +/// @brief whether std::basic_string has a non-deprecated std::char_traits +/// specialization, and is therefore usable as output_adapter's default StringType +/// +/// std::char_traits is only guaranteed (and, on some standard libraries, only +/// implemented without a deprecation warning) for the character types listed +/// below; std::char_traits for any other T (e.g. std::uint8_t, as used by the +/// binary writers) is a non-standard extension some standard libraries deprecate. +/// See https://github.com/nlohmann/json/issues/5725 item 2. +template +struct is_output_adapter_string_char_type : std::integral_constant < bool, + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value +#if defined(__cpp_lib_char8_t) && (__cpp_lib_char8_t >= 201907L) + || std::is_same::value +#endif + > {}; + +/// @brief placeholder type for output_adapter's StringType and (with JSON_NO_IO +/// undefined) its std::basic_ostream constructor parameter, for CharType +/// with no non-deprecated std::char_traits specialization +/// +/// Never actually used: the StringType- and std::basic_ostream-based +/// output_adapter constructors are neither documented nor tested for such +/// CharType (only the std::vector-based constructor is used for them, by the +/// binary writers). Naming std::basic_string or +/// std::basic_ostream anywhere such a constructor would otherwise be +/// declared - even as an unused default template argument or an unused, +/// never-called overload - instantiates std::char_traits merely to +/// name the type, which is exactly what triggers the deprecation warning this +/// placeholder avoids. +template +struct output_adapter_no_string_type {}; + +// Select output_adapter's default StringType (and, below, its ostream +// constructor's parameter type) via partial specialization, not +// std::conditional: std::conditional requires both T and F to be named +// as template arguments up front, which would still instantiate (and thus name) +// std::basic_string / std::basic_ostream for every CharType, +// defeating the point. A bool non-type parameter with two specializations only +// ever names the type that is actually selected. +template::value> +struct output_adapter_default_string_type +{ + using type = output_adapter_no_string_type; +}; + +template +struct output_adapter_default_string_type +{ + using type = std::basic_string; +}; + +#ifndef JSON_NO_IO +/// distinct from output_adapter_no_string_type, so the placeholder overloads of +/// output_adapter's constructor (used when CharType is not a character type) +/// stay distinct overloads instead of colliding into a single redeclaration +template +struct output_adapter_no_ostream_type {}; + +template::value> +struct output_adapter_ostream_type +{ + using type = output_adapter_no_ostream_type; +}; + +template +struct output_adapter_ostream_type +{ + using type = std::basic_ostream; +}; +#endif // JSON_NO_IO + +template < typename CharType, typename StringType = + typename output_adapter_default_string_type::type > class output_adapter { public: @@ -201,7 +277,7 @@ class output_adapter : oa(std::make_shared>(vec)) {} #ifndef JSON_NO_IO - output_adapter(std::basic_ostream& s) + output_adapter(typename output_adapter_ostream_type::type& s) : oa(std::make_shared>(s)) {} #endif // JSON_NO_IO diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 73210904e..c74ae6ac9 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -20143,6 +20143,7 @@ NLOHMANN_JSON_NAMESPACE_END #include // back_inserter #include // shared_ptr, make_shared #include // basic_string +#include // conditional, integral_constant, is_same #include // move #include // vector @@ -20323,7 +20324,82 @@ class output_adapter_sink output_adapter_t oa; }; -template> +/// @brief whether std::basic_string has a non-deprecated std::char_traits +/// specialization, and is therefore usable as output_adapter's default StringType +/// +/// std::char_traits is only guaranteed (and, on some standard libraries, only +/// implemented without a deprecation warning) for the character types listed +/// below; std::char_traits for any other T (e.g. std::uint8_t, as used by the +/// binary writers) is a non-standard extension some standard libraries deprecate. +/// See https://github.com/nlohmann/json/issues/5725 item 2. +template +struct is_output_adapter_string_char_type : std::integral_constant < bool, + std::is_same::value || + std::is_same::value || + std::is_same::value || + std::is_same::value +#if defined(__cpp_lib_char8_t) && (__cpp_lib_char8_t >= 201907L) + || std::is_same::value +#endif + > {}; + +/// @brief placeholder type for output_adapter's StringType and (with JSON_NO_IO +/// undefined) its std::basic_ostream constructor parameter, for CharType +/// with no non-deprecated std::char_traits specialization +/// +/// Never actually used: the StringType- and std::basic_ostream-based +/// output_adapter constructors are neither documented nor tested for such +/// CharType (only the std::vector-based constructor is used for them, by the +/// binary writers). Naming std::basic_string or +/// std::basic_ostream anywhere such a constructor would otherwise be +/// declared - even as an unused default template argument or an unused, +/// never-called overload - instantiates std::char_traits merely to +/// name the type, which is exactly what triggers the deprecation warning this +/// placeholder avoids. +template +struct output_adapter_no_string_type {}; + +// Select output_adapter's default StringType (and, below, its ostream +// constructor's parameter type) via partial specialization, not +// std::conditional: std::conditional requires both T and F to be named +// as template arguments up front, which would still instantiate (and thus name) +// std::basic_string / std::basic_ostream for every CharType, +// defeating the point. A bool non-type parameter with two specializations only +// ever names the type that is actually selected. +template::value> +struct output_adapter_default_string_type +{ + using type = output_adapter_no_string_type; +}; + +template +struct output_adapter_default_string_type +{ + using type = std::basic_string; +}; + +#ifndef JSON_NO_IO +/// distinct from output_adapter_no_string_type, so the placeholder overloads of +/// output_adapter's constructor (used when CharType is not a character type) +/// stay distinct overloads instead of colliding into a single redeclaration +template +struct output_adapter_no_ostream_type {}; + +template::value> +struct output_adapter_ostream_type +{ + using type = output_adapter_no_ostream_type; +}; + +template +struct output_adapter_ostream_type +{ + using type = std::basic_ostream; +}; +#endif // JSON_NO_IO + +template < typename CharType, typename StringType = + typename output_adapter_default_string_type::type > class output_adapter { public: @@ -20332,7 +20408,7 @@ class output_adapter : oa(std::make_shared>(vec)) {} #ifndef JSON_NO_IO - output_adapter(std::basic_ostream& s) + output_adapter(typename output_adapter_ostream_type::type& s) : oa(std::make_shared>(s)) {} #endif // JSON_NO_IO