From af23f07f8cdf504e19c212aea7b794607bf4eff4 Mon Sep 17 00:00:00 2001 From: Niels Lohmann Date: Wed, 11 Nov 2026 08:37:38 +0100 Subject: [PATCH] Fix parse(container, function) for plain functions as callback Passing a plain function or function pointer as parser callback, as in json::parse(std::string("[1,2]"), cb), stopped compiling in C++11/14/17. The iterator+sentinel overloads added for C++20 ranges are constrained by can_compare_ne, i.e. whether `first != last` is well-formed. The callback's parameter types make basic_json an associated class, so ADL finds the friend operator!=(const_reference, ScalarType): the container converts to basic_json and the function pointer is a scalar. The sentinel overload was thereby selected and failed hard in its body. can_compare_ne now excludes function pointers and functions as sentinel type, since a callback is never a sentinel. This shared constraint guards parse, accept, sax_parse and all from_* overloads. Checking IteratorType via std::iterator_traits was avoided on purpose: it would instantiate iterator_traits for every two-argument call, which is a hard error on standard libraries with a non-SFINAE-friendly iterator_traits. Signed-off-by: Niels Lohmann --- .../nlohmann/detail/input/input_adapters.hpp | 20 +++++++--- single_include/nlohmann/json.hpp | 20 +++++++--- tests/src/unit-class_parser.cpp | 37 +++++++++++++++++++ 3 files changed, 65 insertions(+), 12 deletions(-) diff --git a/include/nlohmann/detail/input/input_adapters.hpp b/include/nlohmann/detail/input/input_adapters.hpp index 96d4691ef..b4c998259 100644 --- a/include/nlohmann/detail/input/input_adapters.hpp +++ b/include/nlohmann/detail/input/input_adapters.hpp @@ -1312,15 +1312,23 @@ struct can_compare_ne_either_order : std::integral_constant < bool, can_compare_ne_impl::value || can_compare_ne_reversed::value > {}; -// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a -// trailing default argument (e.g. parse(s, nullptr, ...)) must never be -// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably -// SFINAE the `operator!=` detection above for std::nullptr_t against -// container/string types, which would otherwise make such calls ambiguous -// with the compatible-input overload. +// Two kinds of SentinelType are excluded explicitly: +// +// - std::nullptr_t: a literal `nullptr` passed as a trailing default argument +// (e.g. parse(s, nullptr, ...)) must never be mistaken for a sentinel, and +// some compilers (e.g. GCC 4.8) unreliably SFINAE the `operator!=` detection +// above for std::nullptr_t against container/string types, which would +// otherwise make such calls ambiguous with the compatible-input overload. +// - function pointers and functions: a parser callback passed as plain function +// (e.g. parse(s, cb)) is never a sentinel. The callback's parameter types +// make basic_json an associated class, so ADL finds the friend +// operator!=(const_reference, ScalarType) of basic_json, which makes +// `s != cb` well-formed (both convert to basic_json) and would select the +// iterator/sentinel overload. template struct can_compare_ne : std::integral_constant < bool, !std::is_same::value && + !std::is_function::type>::value && can_compare_ne_either_order::value > {}; template diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 0db1793ac..0adc91c08 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -10421,15 +10421,23 @@ struct can_compare_ne_either_order : std::integral_constant < bool, can_compare_ne_impl::value || can_compare_ne_reversed::value > {}; -// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a -// trailing default argument (e.g. parse(s, nullptr, ...)) must never be -// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably -// SFINAE the `operator!=` detection above for std::nullptr_t against -// container/string types, which would otherwise make such calls ambiguous -// with the compatible-input overload. +// Two kinds of SentinelType are excluded explicitly: +// +// - std::nullptr_t: a literal `nullptr` passed as a trailing default argument +// (e.g. parse(s, nullptr, ...)) must never be mistaken for a sentinel, and +// some compilers (e.g. GCC 4.8) unreliably SFINAE the `operator!=` detection +// above for std::nullptr_t against container/string types, which would +// otherwise make such calls ambiguous with the compatible-input overload. +// - function pointers and functions: a parser callback passed as plain function +// (e.g. parse(s, cb)) is never a sentinel. The callback's parameter types +// make basic_json an associated class, so ADL finds the friend +// operator!=(const_reference, ScalarType) of basic_json, which makes +// `s != cb` well-formed (both convert to basic_json) and would select the +// iterator/sentinel overload. template struct can_compare_ne : std::integral_constant < bool, !std::is_same::value && + !std::is_function::type>::value && can_compare_ne_either_order::value > {}; template diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 987e25858..412d0dcb3 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -3232,3 +3232,40 @@ TEST_CASE("object keys are delivered decoded and validated") CHECK(keys_of_format(std::string("\x87\xC3\x28\x91"), json::input_format_t::bon8).empty()); } } + +namespace +{ +// a plain function (not a lambda or std::function) used as parser callback +bool keep_everything_callback(int /*depth*/, json::parse_event_t /*event*/, json& /*parsed*/) +{ + return true; +} +} // namespace + +TEST_CASE("a plain function is accepted as parser callback for containers") +{ + // ADL finds basic_json's friend operator!= for "container != function + // pointer", which must not make the iterator/sentinel overloads viable + + const json expected = {1, 2}; + const std::string text = "[1,2]"; + const std::vector bytes(text.begin(), text.end()); + const std::vector chars(text.begin(), text.end()); + + CHECK(json::parse(text, keep_everything_callback) == expected); + CHECK(json::parse(text, &keep_everything_callback) == expected); + CHECK(json::parse(std::string("[1,2]"), keep_everything_callback) == expected); + CHECK(json::parse(bytes, keep_everything_callback) == expected); + CHECK(json::parse(bytes, &keep_everything_callback) == expected); + CHECK(json::parse(chars, keep_everything_callback) == expected); + CHECK(json::parse(text.begin(), text.end(), keep_everything_callback) == expected); + + // the sentinel overloads (shared constraint of parse, accept, sax_parse and + // the from_* functions) are not viable for a container and a function + // pointer, but still are for iterator pairs + using callback_pointer = decltype(&keep_everything_callback); + CHECK_FALSE((nlohmann::detail::can_compare_ne::value)); + CHECK_FALSE((nlohmann::detail::can_compare_ne, callback_pointer>::value)); + CHECK((nlohmann::detail::can_compare_ne::value)); + CHECK((nlohmann::detail::can_compare_ne::value)); +}