Compare commits

...
Author SHA1 Message Date
Niels Lohmann af23f07f8c 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 <mail@nlohmann.me>
2026-11-11 09:24:05 +01:00
3 changed files with 65 additions and 12 deletions

No files matched your search

@@ -1312,15 +1312,23 @@ struct can_compare_ne_either_order : std::integral_constant < bool,
can_compare_ne_impl<IteratorType, SentinelType>::value || can_compare_ne_impl<IteratorType, SentinelType>::value ||
can_compare_ne_reversed<IteratorType, SentinelType>::value > {}; can_compare_ne_reversed<IteratorType, SentinelType>::value > {};
// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a // Two kinds of SentinelType are excluded explicitly:
// trailing default argument (e.g. parse(s, nullptr, ...)) must never be //
// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably // - std::nullptr_t: a literal `nullptr` passed as a trailing default argument
// SFINAE the `operator!=` detection above for std::nullptr_t against // (e.g. parse(s, nullptr, ...)) must never be mistaken for a sentinel, and
// container/string types, which would otherwise make such calls ambiguous // some compilers (e.g. GCC 4.8) unreliably SFINAE the `operator!=` detection
// with the compatible-input overload. // 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<typename IteratorType, typename SentinelType> template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool, struct can_compare_ne : std::integral_constant < bool,
!std::is_same<SentinelType, std::nullptr_t>::value && !std::is_same<SentinelType, std::nullptr_t>::value &&
!std::is_function<typename std::remove_pointer<SentinelType>::type>::value &&
can_compare_ne_either_order<IteratorType, SentinelType>::value > {}; can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
template<typename T> template<typename T>
+14 -6
View File
@@ -10421,15 +10421,23 @@ struct can_compare_ne_either_order : std::integral_constant < bool,
can_compare_ne_impl<IteratorType, SentinelType>::value || can_compare_ne_impl<IteratorType, SentinelType>::value ||
can_compare_ne_reversed<IteratorType, SentinelType>::value > {}; can_compare_ne_reversed<IteratorType, SentinelType>::value > {};
// std::nullptr_t is excluded explicitly: a literal `nullptr` passed as a // Two kinds of SentinelType are excluded explicitly:
// trailing default argument (e.g. parse(s, nullptr, ...)) must never be //
// mistaken for a sentinel, and some compilers (e.g. GCC 4.8) unreliably // - std::nullptr_t: a literal `nullptr` passed as a trailing default argument
// SFINAE the `operator!=` detection above for std::nullptr_t against // (e.g. parse(s, nullptr, ...)) must never be mistaken for a sentinel, and
// container/string types, which would otherwise make such calls ambiguous // some compilers (e.g. GCC 4.8) unreliably SFINAE the `operator!=` detection
// with the compatible-input overload. // 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<typename IteratorType, typename SentinelType> template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool, struct can_compare_ne : std::integral_constant < bool,
!std::is_same<SentinelType, std::nullptr_t>::value && !std::is_same<SentinelType, std::nullptr_t>::value &&
!std::is_function<typename std::remove_pointer<SentinelType>::type>::value &&
can_compare_ne_either_order<IteratorType, SentinelType>::value > {}; can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
template<typename T> template<typename T>
+37
View File
@@ -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()); 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<std::uint8_t> bytes(text.begin(), text.end());
const std::vector<char> 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<std::string, callback_pointer>::value));
CHECK_FALSE((nlohmann::detail::can_compare_ne<std::vector<std::uint8_t>, callback_pointer>::value));
CHECK((nlohmann::detail::can_compare_ne<std::string::const_iterator, std::string::const_iterator>::value));
CHECK((nlohmann::detail::can_compare_ne<const char*, const char*>::value));
}