Files
json/include/nlohmann/detail/iterators/iter_impl.hpp
T
Niels Lohmann e5a89d671f Fix lint debt: enum-macro NOLINTs, doctest as SYSTEM, no-op analyzer (#5737)
* Drop stale LCOV_EXCL_LINE from the json_pointer out_of_range.410 throw

The comment said the size_type overflow check in array_index() is only
triggered on special platforms like 32-bit, and the throw was excluded
from coverage. On 64-bit platforms the check is true for SIZE_MAX
itself, and unit-json_pointer.cpp has asserted that case four times
since #5395, so the line is executed in the coverage job. Reword the
comment and remove the exclusion marker so the coverage report notices
if the tests stop reaching it.

Part of #5725

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Name all three C-array check aliases in the enum-macro NOLINTs

NLOHMANN_JSON_SERIALIZE_ENUM(_STRICT) suppressed the c-array warning
under modernize-avoid-c-arrays only, but clang-tidy emits the same
diagnostic under the aliases cppcoreguidelines-avoid-c-arrays and
hicpp-avoid-c-arrays too. Any user running those checks got a false
positive at every macro expansion, and our own tests needed a local
NOLINT at each call site to work around it.

Name all three aliases in the four macro comments instead, and drop
the now-redundant c-array names from the five test call-site NOLINTs.
Comment-only change; behavior, the public API, and the ABI do not
change. Ran make amalgamate.

Part of #5725

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Include doctest as a SYSTEM directory instead of disabling warnings for all tests

test_main added -Wno-deprecated and -Wno-float-equal as PUBLIC compile
options for every non-MSVC compiler, so they were applied to every
translation unit, library headers included, and silenced the CI
warnings meant to check the library's own -Wfloat-equal pragmas. The
only code that actually needed the suppression was the vendored
doctest.h, which was included as a normal (non-SYSTEM) directory.

Include thirdparty/doctest as SYSTEM for test_main, matching what
tests/abi/CMakeLists.txt already does, and drop the two suppressions
from both targets. Verified locally that unit-comparison,
unit-conversions and unit-constructor1 compile clean with
-Werror -Weverything and doctest as -isystem, and that CMake still
configures with JSON_BuildTests=ON.

Part of #5725

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Remove the no-op ci_clang_analyze target

ci_clang_analyze configured the build with the real compiler and only
then wrapped ninja with scan-build. scan-build intercepts compiles by
overriding CC/CXX, but build.ninja already had the compiler path baked
in from the configure step, so every run bypassed the analyzer: CI
logs show "No bugs found" after a normal build, never an analysis.
The job also used Debian's frozen clang-tools-14 rather than the
image's own clang, and CLANG_ANALYZER_CHECKS still named three
valist.* checkers that current clang merged into security.VAList.

ci_clang_tidy already runs every clang-analyzer-* check (via
.clang-tidy's "Checks: '*'") with warnings as errors, so nothing is
lost by removing the dead job. Delete ci_clang_analyze,
CLANG_ANALYZER_CHECKS and the SCAN_BUILD_TOOL lookup from
cmake/ci.cmake, drop it from the ubuntu.yml ci_static_analysis_clang
matrix, and drop the now-unused clang-tools apt package (iwyu stays
for ci_single_binaries). Reword quality_assurance.md and
assurance_case.md, which described the dead job as a working control,
to say the Clang Static Analyzer checks run through clang-tidy.

Verified that `cmake -DJSON_CI=ON` still configures cleanly and that
ci_clang_analyze no longer appears in the generated build or in any
CMake/workflow file.

Part of #5725

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Re-enable portability-template-virtual-member-function; remove redundant forwards

.clang-tidy disabled three checks "to get the CI going" (#4489,
2024-11-13): portability-template-virtual-member-function,
bugprone-use-after-move and its alias hicpp-invalid-access-moved.

portability-template-virtual-member-function only flagged
output_stream_adapter::write_character/write_characters; annotate
both with NOLINT and re-enable the check.

bugprone-use-after-move flagged several double forwards that have no
effect at runtime:
- from_json.hpp calls std::forward<BasicJsonType>(j).at(Idx) inside
  pack expansions; at() has no ref-qualified overloads and always
  returns an lvalue reference, so the forward is a no-op. Replace with
  plain j.at(Idx) in all four places.
- the move constructor forwards the whole object to its base class
  and then reads other's members. That is item 9 of #5724 (together
  with its cppcheck suppressions) and is left to that change.
- input_adapters.hpp forwards the container twice on purpose, so the
  begin/end iterator types match adapter_type; annotate with NOLINT
  and a comment instead of changing behavior.

The check still flags the move constructor (see above) and two sites
in at(KeyType&&) (both overloads, json.hpp, in the throw's
string_t(std::forward<KeyType>(key)) after
find(std::forward<KeyType>(key))). Open PR #5689 rewrites that hunk,
so bugprone-use-after-move (and hicpp-invalid-access-moved)
stay disabled for now, with a comment explaining why; re-enable them
once #5689 and the #5724 move-constructor change have landed.

Also resolve the portability-avoid-pragma-once TODO: single_include
never has #pragma once (amalgamate.py strips it) and every supported
compiler accepts it in include/, so keep it disabled with an
explanatory comment instead of a TODO. Fix the stale "json.hpp,
around line 1265" comment in unit-class_parser.cpp, which now points
at the move constructor's actual line.

Behavior, the public API and the ABI do not change. Verified with
clang-tidy 22.1.8 that portability-template-virtual-member-function
now reports nothing, that bugprone-use-after-move/
hicpp-invalid-access-moved report only the known at(KeyType&&) and
move-constructor sites, and that unit-custom-base-class, unit-constructor1,
unit-conversions, unit-element_access2, unit-class_parser and
unit-diagnostic-positions (JSON_DIAGNOSTIC_POSITIONS=1) compile
under ASan/UBSan and pass with the same assertion counts as before.
Ran make amalgamate.

Part of #5725

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Fix stale and malformed NOLINT comments

json_sax.hpp named "-warnings-as-errors" in the NOLINT list on the two
JSON_ASSERT(false) lines; that is the suffix clang-tidy appends to a
diagnostic tag under WarningsAsErrors, not a check name, and every
other JSON_ASSERT(false) omits it.

unit-capacity.cpp carried 30 "// NOLINT(misc-const-correctness)"
comments on "json j = ...;" declarations that are all used with
non-const members afterwards, so the check has nothing to report
there.

unit-constructor2.cpp used a blanket "// NOLINT: access after move is
OK here" on a use-after-move that hides every check on the line;
naming bugprone-use-after-move and hicpp-invalid-access-moved keeps
the intent once those checks are re-enabled (#5724).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 10

* Remove stale .clang-tidy entries

-google-runtime-references disabled a check that neither clang-tidy
22.1.8 nor 23.1.2 lists under --list-checks -checks='*'; it was
removed upstream. The commented-out HeaderFilterRegex line has been
unused since the active HeaderFilterRegex was introduced in #2561
(2021).

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 11

* Remove the GCC C++20 -Wignored-attributes pragma in json.hpp

The pragma (added in #5164) claimed to work around the C++ modules
redefinition errors of #5103, but #5103 is about hard errors (e.g.
"redefinition of std::__is_constant_evaluated()", conflicting
std::integral_constant) that ignoring a warning cannot suppress; they
are traced to GCC PR 124430 and reproduce with <map> or <string>
instead of json.hpp too. A GCC 16.2 -std=gnu++20 -fmodules build
following #5103's repro steps still fails with the pragma in place,
and a build of all test TUs with GCC_CXXFLAGS (which enable
-Wignored-attributes) and the pragma removed produces no such
warning. The block only hid a warning class from GCC C++20 users
while suggesting #5103 was handled.

Overlaps #5610, whose hunks touch the closing half of this pragma to
insert the json_literals.hpp include.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 9

* Fix stale doxygen comments hidden by the -Wdocumentation pragma

macro_scope.hpp ignores -Wdocumentation and -Wdocumentation-unknown-command
for the whole library, which also hides genuine documentation mistakes:

- detail::unescape() documented "@return unescaped string" but returns
  void and unescapes its argument in place; reworded to
  "@param[in,out] s string to unescape in place" and dropped the
  bogus @return.
- basic_json::get()'s copy-conversion overload wrote "converted to
  @tparam ValueType" inside @return, which Doxygen and Clang parse as
  a second, malformed @tparam; changed to "@a ValueType", matching the
  two other get() overloads a few lines above that already use it.

This narrows the gap the -Wdocumentation pragma needs to cover; fully
replacing the Doxygen-only commands it also hides (item 2c) is left
for after #5267.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 2

* Fix -Wextra-semi-stmt at its actual source, not assert()

clang_flags.cmake blamed the global -Wno-extra-semi-stmt on assert(),
but assert() expands to an expression under glibc and libc++ and does
not trigger this warning. unit-assert_macro.cpp overrides JSON_ASSERT
with "{if (!(x)) ++assert_counter; }", a bare block followed by a
semicolon at every JSON_ASSERT(...) call site in the library; that
was the actual source of 151 of the 208 -Wextra-semi-stmt sites found
in a Clang 22 -Weverything sweep of the test suite with the flag
removed. Switched to the standard do/while(false) macro idiom, which
does not expand to a statement-plus-semicolon, and corrected the
comment to name the remaining source instead: vendored Doctest's
CAPTURE(x) shim, which already ends in a semicolon.

Verified with clang++ -Wextra-semi-stmt (plus the file's other CI
ignores) that unit-assert_macro.cpp now compiles without any
-Wextra-semi-stmt diagnostic.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 8 (step 1 of 2; step 2 covers the CAPTURE() call sites)

* Drop the redundant semicolon from CAPTURE() call sites; remove -Wno-extra-semi-stmt

doctest_compatibility.h defines CAPTURE(x) as DOCTEST_CAPTURE(x); (with
a trailing semicolon baked into the macro), specifically so call sites
do not need to add one themselves; most of the ~267 call sites already
follow that convention. The remaining 64 call sites across 20 files
wrote "CAPTURE(x);" anyway, turning into a statement plus an empty
statement and triggering -Wextra-semi-stmt. Dropped the redundant
semicolon at each of those sites.

With item 6 having already made vendored Doctest a SYSTEM include, and
this the last known source of -Wextra-semi-stmt findings, removed the
flag from clang_flags.cmake entirely.

Verified with clang++ -Wextra-semi-stmt (plus the file's other CI
ignores) that all 20 touched files, plus a file with no CAPTURE() use
(unit-json_pointer.cpp), compile without any -Wextra-semi-stmt
diagnostic.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

#5725 item 8 (step 2 of 2)

* Switch ci_static_analysis_clang off the frozen LLVM 22 dev image

ubuntu.yml pinned the clang-tidy/clang-tidy-sanitizer/single-binaries
job to silkeh/clang:dev, a tag last pushed 2026-02-18 that reports
"clang version 22.0.0 (...+20251015...)", a pre-release snapshot from
before the LLVM 22 release; the maintainer now updates dev-unstable,
22, and latest instead. Switched to silkeh/clang:22, matching the
other clang jobs on :latest.

Verified with clang-tidy 22.1.8 (the image's actual version) against
this repository's .clang-tidy and library headers what the release
image newly reports compared to :dev:

- readability-redundant-typename fires at ~250 sites across the
  _cpp20-relevant conversion/to_chars headers; the library targets
  C++11 and keeps the typenames, so the check is disabled in
  .clang-tidy, matching how the file already handles checks that
  don't fit a C++11 codebase.
- misc-anonymous-namespace-in-header fires on the two anonymous
  namespaces in from_json.hpp and to_json.hpp; added the alias to
  their existing NOLINT (cert-dcl59-cpp, fuchsia-header-anon-namespaces,
  google-build-namespaces).
- bugprone-std-namespace-modification fires on every addition to
  namespace std: the std::hash, std::formatter and std::swap
  overloads in json.hpp, and the std::tuple_size/std::tuple_element
  specializations in iteration_proxy.hpp (this last file is not named
  in #5725's item 5, found by actually running clang-tidy 22.1.8
  against the current tree). All six are legal, deliberate additions
  to namespace std (explicit/partial specializations of std types, or
  the pre-C++20 std::swap overload); annotated each with the check
  name next to its existing cert-dcl58-cpp NOLINT.
- modernize-avoid-c-style-cast reported nothing new.

Also added clang++-22/21, clang-tidy-22/21, g++-16 and gcov-16 to the
find_program search lists in ci.cmake so a local "maximal warnings"
configure prefers the current toolchain version over an older one on
PATH.

#5725 item 5

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Regenerate cmake/gcc_flags.cmake for GCC 16.2.0

GCC_CXXFLAGS was generated for GCC 15.1.0, but ci_test_gcc and
ci_test_gcc_cxx{11..26} now run in gcc:latest, currently GCC 16.2.0,
so the "maximal warnings" job was missing warnings introduced since
15.1.0 while carrying entries GCC 16 treats as duplicates or no-ops.

Regenerated with https://github.com/nlohmann/gcc_flags (patched
locally to not crash on an option whose "-x c++ <opt> -" probe fails
before it reads stdin, e.g. -Wabi=; the tool otherwise raises
BrokenPipeError instead of recording the option as an error) run
against g++ 16.2.0 in the official gcc:16 Docker image, keeping the
documented -Wno-* exclusions and the same alphabetical placement
scheme as before.

Also added three GCC 16 warnings the generator cannot discover on its
own because it only probes value ranges/lists it finds in the -Q
option name itself, not in the enum choices --help=warnings documents
separately:

- -Wbidi-chars=any, -Wleading-whitespace=spaces: manually verified
  these compile cleanly with g++ 16.2.0.
- -Wstrict-flex-arrays: deliberately NOT added, unlike the other two.
  Without -fstrict-flex-arrays (which the library does not enable, as
  it would change codegen for flexible array members), GCC prints
  "'-Wstrict-flex-arrays' is ignored when '-fstrict-flex-arrays' is
  not present" on every translation unit, and under our -Werror that
  note itself aborts the build. This differs from the harmless
  no-op warnings already kept in the file (-Whsa, -Wsynth,
  -Wunreachable-code, -Wunsafe-loop-optimizations), which emit
  nothing; #5725 item 7 named -Wstrict-flex-arrays as one of the
  flags GCC 16 adds, but did not anticipate this failure mode.

Verified: compiled the library header and a representative set of
test translation units (including ones touched by items 1, 3, 8, 9,
10 of this issue) with the regenerated GCC_CXXFLAGS plus -Werror
under g++ 16.2.0 at -std=c++11 through -std=c++26, with zero warnings;
ran the full local test suite (129/129 passing, unrelated to this
compiler) as a regression check. CI must still confirm the actual
ci_test_gcc / ci_test_standards_gcc targets end to end, since this was
verified with direct g++ invocations rather than through the CMake/
CXXFLAGS environment-variable plumbing in ci.cmake.

#5725 item 7

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Avoid std::basic_string<CharType> for non-character output_adapter CharType

output_adapter<CharType, StringType> defaulted StringType to
std::basic_string<CharType>, and (with JSON_NO_IO undefined) always
declared a std::basic_ostream<CharType>&-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<CharType> 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<std::uint8_t>& got one -Wdeprecated-declarations
warning per binary writer at the old output_adapters.hpp:193.

Replaced the eager std::basic_string<CharType> / std::basic_ostream
<CharType> 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<CharType> / std::basic_ostream
<CharType> 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<std::uint8_t>& or
std::basic_ostream<std::uint8_t>& 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<CharType>, std::basic_ostream<CharType>
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<std::uint8_t> now produces no char_traits<unsigned char>
(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 <mail@nlohmann.me>

* Remove the library-wide -Wdocumentation pragma; fix what it hid

macro_scope.hpp / macro_unscope.hpp pushed and popped a Clang
diagnostic region over the entire library that ignored -Wdocumentation
and -Wdocumentation-unknown-command. Removed both pragmas and fixed
every finding a full -Wdocumentation (which implies
-Wdocumentation-unknown-command and -Wdocumentation-deprecated-sync)
build reports, so the library now compiles clean under Clang's
documentation checks without a blanket suppression. Overlaps #5267,
which is still open and edits a nearby doc block (json.hpp's
get()/get_impl() @return, already fixed in the item 2 step (b) commit
of this branch); this commit does not touch that block again.

Unknown Doxygen alias commands (Doxyfile removed in #3071, so these
were never rendered by anything) rewritten as plain prose, keeping the
same information:
- @requirement REQ-JSON-01 / REQ-JSON-02 (iter_impl.hpp,
  json_reverse_iterator.hpp): now "This class satisfies the following
  concept requirements (REQ-JSON-0N):".
- @liveexample{prose,example-id} (three sites in json.hpp): kept the
  prose, dropped the command wrapper and the trailing example-id
  (docs/mkdocs/docs/examples/*.cpp still exist and are used directly
  by the rendered docs, not through this in-header alias) and
  unescaped the "\," commas that were only needed for the old alias's
  comma-separated argument syntax.
- @complexity X (json.hpp x4, json_pointer.hpp x2, serializer.hpp x1):
  now "Complexity: X".

Backslash sequences Clang's comment lexer tried to parse as commands,
escaped to render as literal backslashes:
- lexer.hpp get_codepoint(): two `\u` occurrences.
- binary_reader.hpp get_bson_cstr() / get_bson_cstr_bulk(): two
  `\x00` occurrences.
- serializer.hpp: three `\uXXXX` occurrences (constructor @param,
  append_codepoint_to_string_buffer() @brief, and the ensure_ascii
  member comment).

One finding remained after all of the above: Clang reports
"declaration is marked with '@deprecated' command but does not have a
deprecation attribute" on the deprecated sax_parse(span_input_adapter&&, ...)
overload, even though JSON_HEDLEY_DEPRECATED_FOR does expand to
__attribute__((deprecated(...))) for Clang. Several isolated
reproductions of this exact declaration shape - doc comment,
template<>, two stacked __attribute__ macros, an overload set sharing
the name - did not reproduce the warning, so this looks like a
Clang comment/declaration-association quirk specific to this overload
inside the much larger basic_json class template, not an actual
documentation defect. Rather than keep the pragma library-wide for one
Clang false positive, added a tightly scoped
-Wdocumentation-deprecated-sync push/pop around just that overload.

Verified with Apple clang 21 and the project's actual -Weverything
flag set (cmake/clang_flags.cmake) on the full header at -std=c++11
and -std=c++20: zero -Wdocumentation* diagnostics. Also compiled
clean with GCC 16.2.0 (the pragmas are already __clang__-gated, so
this only confirms no unrelated breakage). Ran make check-amalgamation
and the full local test suite: 129/129 passing.

#5725 item 2 (step c)

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

* Take the JSON value by const reference in the array and tuple from_json paths

Review feedback on #5737 (gregmarr): once the no-op std::forward calls
are gone, the forwarding references have no purpose. from_json_fn
passes the value as const BasicJsonType&, so these functions were only
ever instantiated with a const lvalue anyway.

The std::array, std::pair and std::tuple overloads of from_json and
their helpers now take const BasicJsonType& and pass j on unchanged.
Because the deduced BasicJsonType is now the plain type, tuple_type and
the static_assert name const BasicJsonType& explicitly, so the
reference checks are unchanged: get<std::tuple<const std::string&>>()
still works, and get<std::tuple<std::string&>>() still fails the same
static_assert. from_json_tuple_get_impl keeps its forwarding reference,
since tuple_type calls it through std::declval.

Behavior, the public API and the ABI do not change. unit-conversions,
unit-constructor1, unit-udt, unit-udt_macro, unit-regression1/2/3,
unit-deserialization, unit-noexcept, unit-items, unit-allocator,
unit-custom-object-type, unit-ordered_json2 and
unit-brace-init-copy-semantics pass at C++11, C++17 and C++20 with
unchanged assertion counts. Ran make amalgamate.

Signed-off-by: Niels Lohmann <mail@nlohmann.me>

---------

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-10-01 07:37:47 +02:00

766 lines
25 KiB
C++

// __ _____ _____ _____
// __| | __| | | | JSON for Modern C++
// | | |__ | | | | | | version 3.12.0
// |_____|_____|_____|_|___| https://github.com/nlohmann/json
//
// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann <https://nlohmann.me>
// SPDX-License-Identifier: MIT
#pragma once
#include <iterator> // iterator, random_access_iterator_tag, bidirectional_iterator_tag, advance, next
#include <type_traits> // conditional, is_const, remove_const
#include <nlohmann/detail/exceptions.hpp>
#include <nlohmann/detail/iterators/internal_iterator.hpp>
#include <nlohmann/detail/iterators/primitive_iterator.hpp>
#include <nlohmann/detail/macro_scope.hpp>
#include <nlohmann/detail/meta/cpp_future.hpp>
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
NLOHMANN_JSON_NAMESPACE_BEGIN
namespace detail
{
// forward declare to be able to friend it later on
template<typename IteratorType> class iteration_proxy;
template<typename IteratorType> class iteration_proxy_value;
/*!
@brief a template for a bidirectional iterator for the @ref basic_json class
This class implements a both iterators (iterator and const_iterator) for the
@ref basic_json class.
@note An iterator is called *initialized* when a pointer to a JSON value has
been set (e.g., by a constructor or a copy assignment). If the iterator is
default-constructed, it is *uninitialized* and most methods are undefined.
**The library uses assertions to detect calls on uninitialized iterators.**
This class satisfies the following concept requirements (REQ-JSON-01):
-
[BidirectionalIterator](https://en.cppreference.com/w/cpp/named_req/BidirectionalIterator):
The iterator that can be moved can be moved in both directions (i.e.
incremented and decremented).
@since version 1.0.0, simplified in version 2.0.9, change to bidirectional
iterators in version 3.0.0 (see https://github.com/nlohmann/json/issues/593)
*/
template<typename BasicJsonType>
class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-special-member-functions)
{
/// the iterator with BasicJsonType of different const-ness
using other_iter_impl = iter_impl<typename std::conditional<std::is_const<BasicJsonType>::value, typename std::remove_const<BasicJsonType>::type, const BasicJsonType>::type>;
/// allow basic_json to access private members
friend other_iter_impl;
friend BasicJsonType;
friend iteration_proxy<iter_impl>;
friend iteration_proxy_value<iter_impl>;
using object_t = typename BasicJsonType::object_t;
using array_t = typename BasicJsonType::array_t;
// make sure BasicJsonType is basic_json or const basic_json
static_assert(is_basic_json<typename std::remove_const<BasicJsonType>::type>::value,
"iter_impl only accepts (const) basic_json");
// superficial check for the LegacyBidirectionalIterator named requirement
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value
&& std::is_base_of<std::bidirectional_iterator_tag, typename std::iterator_traits<typename array_t::iterator>::iterator_category>::value,
"basic_json iterator assumes array and object type iterators satisfy the LegacyBidirectionalIterator named requirement.");
public:
/// The std::iterator class template (used as a base class to provide typedefs) is deprecated in C++17.
/// The C++ Standard has never required user-defined iterators to derive from std::iterator.
/// A user-defined iterator should provide publicly accessible typedefs named
/// iterator_category, value_type, difference_type, pointer, and reference.
/// Note that value_type is required to be non-const, even for constant iterators.
using iterator_category = std::bidirectional_iterator_tag;
/// the type of the values when the iterator is dereferenced
using value_type = typename BasicJsonType::value_type;
/// a type to represent differences between iterators
using difference_type = typename BasicJsonType::difference_type;
/// defines a pointer to the type iterated over (value_type)
using pointer = typename std::conditional<std::is_const<BasicJsonType>::value,
typename BasicJsonType::const_pointer,
typename BasicJsonType::pointer>::type;
/// defines a reference to the type iterated over (value_type)
using reference =
typename std::conditional<std::is_const<BasicJsonType>::value,
typename BasicJsonType::const_reference,
typename BasicJsonType::reference>::type;
iter_impl() = default;
~iter_impl() = default;
// the exception specification is left to be computed rather than declared:
// an array or object type whose iterator is not nothrow move constructible
// (std::deque's is not before libstdc++ 11) would make a declared noexcept
// differ from the implicit one, which deletes the function -- and is an
// error outright with older compilers
iter_impl(iter_impl&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
iter_impl& operator=(iter_impl&&) = default; // NOLINT(hicpp-noexcept-move,performance-noexcept-move-constructor,cppcoreguidelines-noexcept-move-operations)
/*!
@brief constructor for a given JSON instance
@param[in] object pointer to a JSON object for this iterator
@pre object != nullptr
@post The iterator is initialized; i.e. `m_object != nullptr`.
*/
explicit iter_impl(pointer object) noexcept : m_object(object)
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
m_it.object_iterator = typename object_t::iterator();
break;
}
case value_t::array:
{
m_it.array_iterator = typename array_t::iterator();
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator = primitive_iterator_t();
break;
}
}
}
/*!
@note The conventional copy constructor and copy assignment are implicitly
defined. Combined with the following converting constructor and
assignment, they support: (1) copy from iterator to iterator, (2)
copy from const iterator to const iterator, and (3) conversion from
iterator to const iterator. However conversion from const iterator
to iterator is not defined.
*/
/*!
@brief const copy constructor
@param[in] other const iterator to copy from
@note This copy constructor had to be defined explicitly to circumvent a bug
occurring on msvc v19.0 compiler (VS 2015) debug build. For more
information refer to: https://github.com/nlohmann/json/issues/1608
*/
iter_impl(const iter_impl<const BasicJsonType>& other) noexcept
: m_object(other.m_object), m_it(other.m_it)
{}
/*!
@brief converting assignment
@param[in] other const iterator to copy from
@return const/non-const iterator
@note It is not checked whether @a other is initialized.
*/
iter_impl& operator=(const iter_impl<const BasicJsonType>& other) noexcept
{
if (&other != this)
{
m_object = other.m_object;
m_it = other.m_it;
}
return *this;
}
/*!
@brief converting constructor
@param[in] other non-const iterator to copy from
@note It is not checked whether @a other is initialized.
*/
iter_impl(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept
: m_object(other.m_object), m_it(other.m_it)
{}
/*!
@brief converting assignment
@param[in] other non-const iterator to copy from
@return const/non-const iterator
@note It is not checked whether @a other is initialized.
*/
iter_impl& operator=(const iter_impl<typename std::remove_const<BasicJsonType>::type>& other) noexcept // NOLINT(cert-oop54-cpp)
{
m_object = other.m_object;
m_it = other.m_it;
return *this;
}
JSON_PRIVATE_UNLESS_TESTED:
/*!
@brief set the iterator to the first value
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
void set_begin() noexcept
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
m_it.object_iterator = m_object->m_data.m_value.object->begin();
break;
}
case value_t::array:
{
m_it.array_iterator = m_object->m_data.m_value.array->begin();
break;
}
case value_t::null:
{
// set to end so begin()==end() is true: null is empty
m_it.primitive_iterator.set_end();
break;
}
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator.set_begin();
break;
}
}
}
/*!
@brief set the iterator past the last value
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
void set_end() noexcept
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
m_it.object_iterator = m_object->m_data.m_value.object->end();
break;
}
case value_t::array:
{
m_it.array_iterator = m_object->m_data.m_value.array->end();
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator.set_end();
break;
}
}
}
public:
/*!
@brief return a reference to the value pointed to by the iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
reference operator*() const
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return m_it.object_iterator->second;
}
case value_t::array:
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return *m_it.array_iterator;
}
case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object));
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
{
return *m_object;
}
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object));
}
}
}
/*!
@brief dereference the iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
pointer operator->() const
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
JSON_ASSERT(m_it.object_iterator != m_object->m_data.m_value.object->end());
return &(m_it.object_iterator->second);
}
case value_t::array:
{
JSON_ASSERT(m_it.array_iterator != m_object->m_data.m_value.array->end());
return &*m_it.array_iterator;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.is_begin()))
{
return m_object;
}
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object));
}
}
}
/*!
@brief post-increment (it++)
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl operator++(int)& // NOLINT(cert-dcl21-cpp)
{
auto result = *this;
++(*this);
return result;
}
/*!
@brief pre-increment (++it)
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl& operator++()
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
std::advance(m_it.object_iterator, 1);
break;
}
case value_t::array:
{
std::advance(m_it.array_iterator, 1);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
++m_it.primitive_iterator;
break;
}
}
return *this;
}
/*!
@brief post-decrement (it--)
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl operator--(int)& // NOLINT(cert-dcl21-cpp)
{
auto result = *this;
--(*this);
return result;
}
/*!
@brief pre-decrement (--it)
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl& operator--()
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
{
std::advance(m_it.object_iterator, -1);
break;
}
case value_t::array:
{
std::advance(m_it.array_iterator, -1);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
--m_it.primitive_iterator;
break;
}
}
return *this;
}
/*!
@brief comparison: equal
@pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.
*/
template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >
bool operator==(const IterImpl& other) const
{
// if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object));
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr)
{
return true;
}
switch (m_object->m_data.m_type)
{
case value_t::object:
return (m_it.object_iterator == other.m_it.object_iterator);
case value_t::array:
return (m_it.array_iterator == other.m_it.array_iterator);
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator == other.m_it.primitive_iterator);
}
}
/*!
@brief comparison: not equal
@pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.
*/
template < typename IterImpl, detail::enable_if_t < (std::is_same<IterImpl, iter_impl>::value || std::is_same<IterImpl, other_iter_impl>::value), std::nullptr_t > = nullptr >
bool operator!=(const IterImpl& other) const
{
return !operator==(other);
}
/*!
@brief comparison: smaller
@pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.
*/
bool operator<(const iter_impl& other) const
{
// if objects are not the same, the comparison is undefined
if (JSON_HEDLEY_UNLIKELY(m_object != other.m_object))
{
JSON_THROW(invalid_iterator::create(212, "cannot compare iterators of different containers", m_object));
}
// value-initialized forward iterators can be compared, and must compare equal to other value-initialized iterators of the same type #4493
if (m_object == nullptr)
{
// the iterators are both value-initialized and are to be considered equal, but this function checks for smaller, so we return false
return false;
}
switch (m_object->m_data.m_type)
{
case value_t::object:
JSON_THROW(invalid_iterator::create(213, "cannot compare order of object iterators", m_object));
case value_t::array:
return (m_it.array_iterator < other.m_it.array_iterator);
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return (m_it.primitive_iterator < other.m_it.primitive_iterator);
}
}
/*!
@brief comparison: less than or equal
@pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.
*/
bool operator<=(const iter_impl& other) const
{
return !other.operator < (*this);
}
/*!
@brief comparison: greater than
@pre (1) Both iterators are initialized to point to the same object, or (2) both iterators are value-initialized.
*/
bool operator>(const iter_impl& other) const
{
return !operator<=(other);
}
/*!
@brief comparison: greater than or equal
@pre (1) The iterator is initialized; i.e. `m_object != nullptr`, or (2) both iterators are value-initialized.
*/
bool operator>=(const iter_impl& other) const
{
return !operator<(other);
}
/*!
@brief add to iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl& operator+=(difference_type i)
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object));
case value_t::array:
{
std::advance(m_it.array_iterator, i);
break;
}
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
m_it.primitive_iterator += i;
break;
}
}
return *this;
}
/*!
@brief subtract from iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl& operator-=(difference_type i)
{
return operator+=(-i);
}
/*!
@brief add to iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl operator+(difference_type i) const
{
auto result = *this;
result += i;
return result;
}
/*!
@brief addition of distance and iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
friend iter_impl operator+(difference_type i, const iter_impl& it)
{
auto result = it;
result += i;
return result;
}
/*!
@brief subtract from iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
iter_impl operator-(difference_type i) const
{
auto result = *this;
result -= i;
return result;
}
/*!
@brief return difference
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
difference_type operator-(const iter_impl& other) const
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
JSON_THROW(invalid_iterator::create(209, "cannot use offsets with object iterators", m_object));
case value_t::array:
return m_it.array_iterator - other.m_it.array_iterator;
case value_t::null:
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
return m_it.primitive_iterator - other.m_it.primitive_iterator;
}
}
/*!
@brief access to successor
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
reference operator[](difference_type n) const
{
JSON_ASSERT(m_object != nullptr);
switch (m_object->m_data.m_type)
{
case value_t::object:
JSON_THROW(invalid_iterator::create(208, "cannot use operator[] for object iterators", m_object));
case value_t::array:
return *std::next(m_it.array_iterator, n);
case value_t::null:
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object));
case value_t::string:
case value_t::boolean:
case value_t::number_integer:
case value_t::number_unsigned:
case value_t::number_float:
case value_t::binary:
case value_t::discarded:
default:
{
if (JSON_HEDLEY_LIKELY(m_it.primitive_iterator.get_value() == -n))
{
return *m_object;
}
JSON_THROW(invalid_iterator::create(214, "cannot get value", m_object));
}
}
}
/*!
@brief return the key of an object iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
const typename object_t::key_type& key() const
{
JSON_ASSERT(m_object != nullptr);
if (JSON_HEDLEY_LIKELY(m_object->is_object()))
{
return m_it.object_iterator->first;
}
JSON_THROW(invalid_iterator::create(207, "cannot use key() for non-object iterators", m_object));
}
/*!
@brief return the value of an iterator
@pre The iterator is initialized; i.e. `m_object != nullptr`.
*/
reference value() const
{
return operator*();
}
JSON_PRIVATE_UNLESS_TESTED:
/// associated JSON instance
pointer m_object = nullptr;
/// the actual iterator of the associated instance
internal_iterator<typename std::remove_const<BasicJsonType>::type> m_it {};
};
} // namespace detail
NLOHMANN_JSON_NAMESPACE_END