Merge iterator+sentinel overloads and fix ambiguity/CI issues

Address PR review feedback and CI failures:

- Merge the separate same-type and sentinel-type iterator overloads of
  parse(), accept(), sax_parse(), and the five from_* binary deserializers
  into a single overload with SentinelType defaulted to IteratorType,
  as suggested in review. Applied the same simplification to the
  detail::input_adapter() free functions.
- Fix a latent ambiguity: some compilers (e.g. GCC 4.8) unreliably SFINAE
  the operator!= detection for std::nullptr_t against container/string
  types, making calls like parse(s, nullptr, ...) ambiguous with the
  compatible-input overload. can_compare_ne now explicitly excludes
  std::nullptr_t as a SentinelType.
- Use a named enable_if_t template parameter instead of an unnamed
  function parameter for the SFINAE guard, fixing a clang-tidy
  hicpp-named-parameter/readability-named-parameter failure.
- Update parse.md, accept.md, sax_parse.md, and the five from_*.md pages
  to document the merged overload instead of separate (2)/(3) overloads,
  also fixing an over-160-char line that broke the documentation
  style_check CI job.
- Rework the BSON iterator+sentinel test to parse a BSON file already
  present in the test suite instead of writing/deleting a temp file.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
This commit is contained in:
Niels Lohmann
2026-07-10 23:15:17 +02:00
parent 2269656bc6
commit 146e5e0bc7
12 changed files with 147 additions and 460 deletions
@@ -491,10 +491,21 @@ struct can_compare_ne_reversed < IteratorType, SentinelType,
: std::true_type {};
template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool,
struct can_compare_ne_either_order : std::integral_constant < bool,
can_compare_ne_impl<IteratorType, SentinelType>::value ||
can_compare_ne_reversed<IteratorType, SentinelType>::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.
template<typename IteratorType, typename SentinelType>
struct can_compare_ne : std::integral_constant < bool,
!std::is_same<SentinelType, std::nullptr_t>::value &&
can_compare_ne_either_order<IteratorType, SentinelType>::value > {};
template<typename T>
struct is_iterator_of_multibyte
{
@@ -520,19 +531,12 @@ struct iterator_input_adapter_factory<IteratorType, SentinelType, enable_if_t<is
}
};
// General purpose iterator-based input (same-type iterators)
template<typename IteratorType>
typename iterator_input_adapter_factory<IteratorType, IteratorType>::adapter_type input_adapter(IteratorType first, IteratorType last)
{
using factory_type = iterator_input_adapter_factory<IteratorType, IteratorType>;
return factory_type::create(first, last);
}
// General purpose iterator-based input (iterator+sentinel pair with different types)
// Only enable for types that can be compared with !=
template < typename IteratorType, typename SentinelType,
// General purpose iterator-based input (iterator+sentinel pair; SentinelType
// defaults to IteratorType for the common same-type case, but may differ for
// C++20 ranges-style iterator+sentinel pairs). Only enable for types that can
// be compared with !=.
template < typename IteratorType, typename SentinelType = IteratorType,
typename = typename std::enable_if <
!std::is_same<IteratorType, SentinelType>::value &&
can_compare_ne<IteratorType, SentinelType>::value >::type >
typename iterator_input_adapter_factory<IteratorType, SentinelType>::adapter_type input_adapter(IteratorType first, SentinelType last)
{
+32 -158
View File
@@ -4083,34 +4083,17 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return result;
}
/// @brief deserialize from a pair of character iterators
/// @brief deserialize from a pair of character iterators (or an iterator+sentinel pair, C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/parse/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(IteratorType first,
IteratorType last,
parser_callback_t cb = nullptr,
const bool allow_exceptions = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
{
basic_json result;
parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved]
return result;
}
/// @brief deserialize from an iterator+sentinel pair (C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/parse/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json parse(IteratorType first,
SentinelType last,
parser_callback_t cb = nullptr,
const bool allow_exceptions = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool ignore_trailing_commas = false)
{
basic_json result;
parser(detail::input_adapter(std::move(first), std::move(last)), std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas).parse(true, result); // cppcheck-suppress[accessMoved]
@@ -4140,24 +4123,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return parser(detail::input_adapter(std::forward<InputType>(i)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
}
/// @brief check if the input is valid JSON
/// @brief check if the input is valid JSON (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType>
static bool accept(IteratorType first, IteratorType last,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
{
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
}
/// @brief check if the input is valid JSON (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/accept/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
static bool accept(IteratorType first, SentinelType last,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool ignore_trailing_commas = false)
{
return parser(detail::input_adapter(std::move(first), std::move(last)), nullptr, false, ignore_comments, ignore_trailing_commas).accept(true);
}
@@ -4187,33 +4159,16 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
}
/// @brief generate SAX events
/// @brief generate SAX events (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
template<class IteratorType, class SAX>
JSON_HEDLEY_NON_NULL(3)
static bool sax_parse(IteratorType first, IteratorType last, SAX* sax,
input_format_t format = input_format_t::json,
const bool strict = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false)
{
auto ia = detail::input_adapter(std::move(first), std::move(last));
return format == input_format_t::json
? parser(std::move(ia), nullptr, true, ignore_comments, ignore_trailing_commas).sax_parse(sax, strict)
: detail::binary_reader<basic_json, decltype(ia), SAX>(std::move(ia), format).sax_parse(format, sax, strict);
}
/// @brief generate SAX events (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/sax_parse/
template<class IteratorType, class SentinelType, class SAX>
template<class IteratorType, class SAX, class SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_NON_NULL(3)
static bool sax_parse(IteratorType first, SentinelType last, SAX* sax,
input_format_t format = input_format_t::json,
const bool strict = true,
const bool ignore_comments = false,
const bool ignore_trailing_commas = false,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool ignore_trailing_commas = false)
{
auto ia = detail::input_adapter(std::move(first), std::move(last));
return format == input_format_t::json
@@ -4509,32 +4464,15 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in CBOR format
/// @brief create a JSON value from an input in CBOR format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_cbor(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::cbor).sax_parse(input_format_t::cbor, &sdp, strict, tag_handler); // cppcheck-suppress[accessMoved]
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in CBOR format (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/from_cbor/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_cbor(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true,
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const cbor_tag_handler_t tag_handler = cbor_tag_handler_t::error)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
@@ -4584,30 +4522,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in MessagePack format
/// @brief create a JSON value from an input in MessagePack format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_msgpack(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::msgpack).sax_parse(input_format_t::msgpack, &sdp, strict); // cppcheck-suppress[accessMoved]
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in MessagePack format (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/from_msgpack/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_msgpack(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
@@ -4655,30 +4577,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in UBJSON format
/// @brief create a JSON value from an input in UBJSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_ubjson(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::ubjson).sax_parse(input_format_t::ubjson, &sdp, strict); // cppcheck-suppress[accessMoved]
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in UBJSON format (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/from_ubjson/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_ubjson(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
@@ -4726,30 +4632,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BJData format
/// @brief create a JSON value from an input in BJData format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bjdata).sax_parse(input_format_t::bjdata, &sdp, strict); // cppcheck-suppress[accessMoved]
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BJData format (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/from_bjdata/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bjdata(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
@@ -4773,30 +4663,14 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BSON format
/// @brief create a JSON value from an input in BSON format (iterator pair, or iterator+sentinel pair for C++20 ranges support)
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename IteratorType>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bson(IteratorType first, IteratorType last,
const bool strict = true,
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));
detail::json_sax_dom_parser<basic_json, decltype(ia)> sdp(result, allow_exceptions);
const bool res = binary_reader<decltype(ia)>(std::move(ia), input_format_t::bson).sax_parse(input_format_t::bson, &sdp, strict); // cppcheck-suppress[accessMoved]
return res ? result : basic_json(value_t::discarded);
}
/// @brief create a JSON value from an input in BSON format (sentinel version)
/// @sa https://json.nlohmann.me/api/basic_json/from_bson/
template<typename IteratorType, typename SentinelType>
template<typename IteratorType, typename SentinelType = IteratorType,
detail::enable_if_t<detail::can_compare_ne<IteratorType, SentinelType>::value, int> = 0>
JSON_HEDLEY_WARN_UNUSED_RESULT
static basic_json from_bson(IteratorType first, SentinelType last,
const bool strict = true,
const bool allow_exceptions = true,
typename std::enable_if < !std::is_same<IteratorType, SentinelType>::value &&
detail::can_compare_ne<IteratorType, SentinelType>::value >::type* = nullptr)
const bool allow_exceptions = true)
{
basic_json result;
auto ia = detail::input_adapter(std::move(first), std::move(last));