Compare commits

..
Author SHA1 Message Date
Niels Lohmann bb62a61c04 Remove two leftover Doxygen-era .link files from the examples directory
parse__iterator_pair.link and parse__pointers.link each held only a
Wandbox "online" permalink from the old Doxygen docs. #3071 deleted
every other .link file in 2021; these two came in through a parallel
PR (#3100) and were never referenced by any page, script or config.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 10:01:38 +02:00
Niels Lohmann f9b9b303cb Fix docset README's non-existent make target and stale fallback URL
The README told readers to run `make nlohmann_json.docset`, but the
Makefile's targets are `all`, `JSON_for_Modern_C++.docset` and
`install_docset_zeal`; the documented command has failed with "No
rule to make target" since #2967 (2021). Point the README at the real
target and folder name. Info.plist's DashDocSetFallbackURL also still
pointed at the old nlohmann.github.io/json/ URL instead of the
canonical https://json.nlohmann.me/ from mkdocs.yml's site_url.

Leave list_missing_pages/list_removed_paths alone: they may become
redundant once #5638's check_docset() lands, which is a follow-up.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 10:01:21 +02:00
Niels Lohmann ed9510ed75 Fix check_structure.py NOLINT parsing and an off-by-one report line
`line.strip("<!-- NOLINT")` followed by `.strip(" -->")` strips any of
the characters in those sets from both ends, not a literal prefix; it
only happened to work for "Examples". A NOLINT'd section name
starting with N, O, L, I or T (e.g. "Notes", "Template parameters",
"Iterator invalidation", "Literals") was silently mangled, so the
suppression did not apply and the checker could report a spurious
missing/misordered section. Parse the comment with a regex instead.
The same fragile strip() pattern was used for heading text; replace it
with a plain prefix slice. Also fix the admonition_title report, which
used the 0-based line index while every other report in the file uses
lineno+1.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 10:00:41 +02:00
Niels Lohmann 3acf3a1c3d Fix example build: broken debug echo, deprecations hidden for everything
docs/Makefile's debug echo used a space instead of a comma in
$(call cxx_standard ...), so it always printed an empty standard.
Every example was also compiled with -Wno-deprecated-declarations,
which would silently hide an accidental deprecated-API call in any of
them. Factor the duplicated compile flags into EXAMPLE_CPPFLAGS/
EXAMPLE_WARNFLAGS, build with -Werror=deprecated-declarations by
default, and only allow the three examples that intentionally
document deprecated API (the DEPRECATED_EXAMPLES list) to suppress it.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 09:59:34 +02:00
Niels Lohmann 3a64ae470b Fix publish_documentation triggers and persist unneeded git credentials
publish_documentation.yml only triggered on docs/mkdocs/** pushes, but
the site also embeds .github/CODE_OF_CONDUCT.md, CONTRIBUTING.md,
SECURITY.md, cmake/{clang,gcc}_flags.cmake, .clang-tidy,
tools/astyle/.astylerc and tests/fmt_formatter/project/main.cpp via
pymdownx.snippets, so changes to those files never republished the
site. Extend the path filter to cover them, and switch runs-on from
the long-pinned ubuntu-22.04 to ubuntu-latest to match
ci_test_documentation.

Also add persist-credentials: false to the checkouts in ci_icpx and
ci_nvhpc (ubuntu.yml) and msvc-vs2026/msvc-arm64 (windows.yml), none
of which pushes with git, matching every other checkout in these
workflows.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 09:54:37 +02:00
Niels Lohmann 32912a5e80 Fix math formulas rendering as raw TeX in the published docs
The privacy plugin self-hosts MathJax 2.7.0 but drops its
?config=TeX-MML-AM_CHTML query string, so the rehosted script loads no
input jax and the 10 formulas across 6 pages render as raw TeX to
readers. Remove pymdownx.arithmatex and the MathJax extra_javascript
entry, and rewrite the formulas in plain HTML (<sup>, <i>) instead.
This also drops a nine-year-old third-party script from every page.

Part of #5718

Signed-off-by: Niels Lohmann <mail@nlohmann.me>
2026-09-30 09:53:33 +02:00
19 changed files with 95 additions and 195 deletions
+11 -1
View File
@@ -7,6 +7,16 @@ on:
- develop
paths:
- docs/mkdocs/**
# the site also embeds these files via pymdownx.snippets
# (mkdocs.yml sets restrict_base_path: false for this)
- .clang-tidy
- .github/CODE_OF_CONDUCT.md
- .github/CONTRIBUTING.md
- .github/SECURITY.md
- cmake/clang_flags.cmake
- cmake/gcc_flags.cmake
- tests/fmt_formatter/project/main.cpp
- tools/astyle/.astylerc
workflow_dispatch:
# we don't want to have concurrent jobs, and we don't want to cancel running jobs to avoid broken publications
@@ -23,7 +33,7 @@ jobs:
contents: write
if: github.repository == 'nlohmann/json'
runs-on: ubuntu-22.04
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@e14015d583714f6e62063499dc959a02595150a1 # v2.21.1
+4
View File
@@ -346,6 +346,8 @@ jobs:
container: intel/oneapi-hpckit:latest
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
- name: Run CMake
@@ -358,6 +360,8 @@ jobs:
container: nvcr.io/nvidia/nvhpc:25.5-devel-cuda12.9-ubuntu22.04
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
- name: Run CMake
+4
View File
@@ -87,6 +87,8 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Get latest CMake and ninja
uses: lukka/get-cmake@fffaaafeea488556c2c12dad60690008bc1caacb # v4.4.2
- name: Set extra CXX_FLAGS for latest std_version
@@ -123,6 +125,8 @@ jobs:
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- name: Run CMake (Release)
run: cmake -S . -B build -G "Visual Studio 18 2026" -A ARM64 -DJSON_BuildTests=On -DCMAKE_CXX_FLAGS="/W4 /WX"
if: matrix.build_type == 'Release'
+16 -5
View File
@@ -11,20 +11,31 @@ EXAMPLES = $(wildcard mkdocs/docs/examples/*.cpp)
cxx_standard = $(lastword c++11 $(filter c++%, $(subst ., ,$1)))
# common compile flags for the stand-alone example files
EXAMPLE_CPPFLAGS = -I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0
EXAMPLE_WARNFLAGS = -Werror=deprecated-declarations
# examples that document deprecated API and are allowed to use it
DEPRECATED_EXAMPLES = $(addprefix mkdocs/docs/examples/, \
json_pointer__operator__equal_stringtype \
json_pointer__operator__notequal_stringtype \
json_pointer__operator_string_t)
$(DEPRECATED_EXAMPLES:=.output) $(DEPRECATED_EXAMPLES:=.test): EXAMPLE_WARNFLAGS = -Wno-deprecated-declarations
# create output from a stand-alone example file
%.output: %.cpp
@echo "standard $(call cxx_standard $(<:.cpp=))"
@echo "standard $(call cxx_standard,$(<:.cpp=))"
$(MAKE) $(<:.cpp=) \
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
./$(<:.cpp=) > $@
rm $(<:.cpp=)
# compare created output with current output of the example files
%.test: %.cpp
$(MAKE) $(<:.cpp=) \
CPPFLAGS="-I $(SRCDIR) -DJSON_USE_GLOBAL_UDLS=0" \
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) -Wno-deprecated-declarations"
CPPFLAGS="$(EXAMPLE_CPPFLAGS)" \
CXXFLAGS="-std=$(call cxx_standard,$(<:.cpp=)) $(EXAMPLE_WARNFLAGS)"
./$(<:.cpp=) > $@
diff $@ $(<:.cpp=.output)
rm $(<:.cpp=) $@
+1 -1
View File
@@ -13,7 +13,7 @@
<key>dashIndexFilePath</key>
<string>index.html</string>
<key>DashDocSetFallbackURL</key>
<string>https://nlohmann.github.io/json/</string>
<string>https://json.nlohmann.me/</string>
<key>isJavaScriptEnabled</key>
<true/>
</dict>
+3 -2
View File
@@ -7,10 +7,11 @@ documentation browsers like [Dash](https://kapeli.com/dash), [Velocity](https://
The docset can be created with
```sh
make nlohmann_json.docset
make JSON_for_Modern_C++.docset
```
The generated folder `nlohmann_json.docset` can then be opened in the documentation browser.
The generated folder `JSON_for_Modern_C++.docset` can then be opened in the documentation browser. `make all` builds a
`JSON_for_Modern_C++.tgz` archive instead, and `make install_docset_zeal` installs the docset for Zeal directly.
A recent version is also part of the [Dash user contributions](https://github.com/Kapeli/Dash-User-Contributions/tree/master/docsets/JSON_for_Modern_C%2B%2B).
@@ -51,7 +51,7 @@ range will yield over/underflow when used in a constructor. During deserializati
will automatically be stored as [`number_unsigned_t`](number_unsigned_t.md) or [`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
> interoperable in the sense that implementations will agree exactly on their numeric values.
As this range is a subrange of the exactly supported range [INT64_MIN, INT64_MAX], this class's integer type is
@@ -52,7 +52,7 @@ when used in a constructor. During deserialization, too large or small integer n
as [`number_integer_t`](number_integer_t.md) or [`number_float_t`](number_float_t.md).
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are
> interoperable in the sense that implementations will agree exactly on their numeric values.
As this range is a subrange (when considered in conjunction with the `number_integer_t` type) of the exactly supported
@@ -54,7 +54,7 @@ classDiagram
## Notes
For an input with $n$ bytes, 1 is the index of the first character and $n+1$ is the index of the terminating null byte
For an input with <i>n</i> bytes, 1 is the index of the first character and <i>n</i>+1 is the index of the terminating null byte
or the end of file. This also holds true when reading a byte vector for binary formats.
## Examples
@@ -1 +0,0 @@
<a target="_blank" href="https://wandbox.org/permlink/hUJYo1HWmfTBLMGn"><b>online</b></a>
@@ -1 +0,0 @@
<a target="_blank" href="https://wandbox.org/permlink/AWbpa8e1xRV3y4MM"><b>online</b></a>
@@ -61,7 +61,7 @@ The library uses the following mapping from JSON values types to BJData types ac
The following values can **not** be converted to a BJData value:
- strings with more than 18446744073709551615 bytes, i.e., $2^{64}-1$ bytes (theoretical)
- strings with more than 18446744073709551615 bytes, i.e., 2<sup>64</sup>-1 bytes (theoretical)
!!! info "Unused BJData markers"
+1 -1
View File
@@ -273,7 +273,7 @@ When the default type is used, the maximal unsigned integer number that can be s
[RFC 8259](https://tools.ietf.org/html/rfc8259) further states:
> Note that when such software is used, numbers that are integers and are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the sense that implementations will agree exactly on their numeric values.
> Note that when such software is used, numbers that are integers and are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the sense that implementations will agree exactly on their numeric values.
As this range is a subrange of the exactly supported range [`INT64_MIN`, `INT64_MAX`], this class's integer type is interoperable.
@@ -48,7 +48,7 @@ On number interoperability, the following remarks are made:
for numeric magnitude and precision than is widely available.
Note that when such software is used, numbers that are integers and
are in the range $[-2^{53}+1, 2^{53}-1]$ are interoperable in the
are in the range [-2<sup>53</sup>+1, 2<sup>53</sup>-1] are interoperable in the
sense that implementations will agree exactly on their numeric
values.
@@ -95,9 +95,9 @@ This is the same behavior as the code `#!c double x = 3.141592653589793238462643
!!! success "Interoperability"
- The library is interoperable with respect to the specification, because its supported range $[-2^{63}, 2^{64}-1]$ is
larger than the described range $[-2^{53}+1, 2^{53}-1]$.
- All integers outside the range $[-2^{63}, 2^{64}-1]$, as well as floating-point numbers are stored as `double`.
- The library is interoperable with respect to the specification, because its supported range [-2<sup>63</sup>, 2<sup>64</sup>-1] is
larger than the described range [-2<sup>53</sup>+1, 2<sup>53</sup>-1].
- All integers outside the range [-2<sup>63</sup>, 2<sup>64</sup>-1], as well as floating-point numbers are stored as `double`.
This also concurs with the specification above.
### Zeros
-4
View File
@@ -349,7 +349,6 @@ markdown_extensions:
- toc:
permalink: true
- md_in_html
- pymdownx.arithmatex
- pymdownx.betterem:
smart_enable: all
- pymdownx.caret
@@ -441,6 +440,3 @@ plugins:
extra_css:
- css/custom.css
extra_javascript:
- https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML
+5 -5
View File
@@ -79,9 +79,9 @@ def check_structure() -> None:
report("whitespace/line_length", f"{file}:{lineno+1} ({current_section})", f"line is too long ({len(line)} vs. 160 chars)")
# sections in `<!-- NOLINT -->` comments are treated as present
if line.startswith("<!-- NOLINT"):
current_section = line.strip("<!-- NOLINT")
current_section = current_section.strip(" -->")
nolint_match = re.match(r"<!--\s*NOLINT\s+(.*?)\s*-->", line)
if nolint_match:
current_section = nolint_match.group(1)
existing_sections.append(current_section)
# check if sections are correct
@@ -97,7 +97,7 @@ def check_structure() -> None:
if len(unexpected):
report("style/numbering", f"{file}:{lineno} ({current_section})", f'unexpected overloads: {", ".join([f"({x})" for x in unexpected])}')
current_section = line.strip("## ")
current_section = line[3:]
existing_sections.append(current_section)
if current_section in expected_sections:
@@ -141,7 +141,7 @@ def check_structure() -> None:
# check that non-example admonitions have titles
untitled_admonition = re.match(r"^(\?\?\?|!!!) ([^ ]+)$", line)
if untitled_admonition and untitled_admonition.group(2) != "example":
report("style/admonition_title", f"{file}:{lineno} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
report("style/admonition_title", f"{file}:{lineno+1} ({current_section})", f'"{untitled_admonition.group(2)}" admonitions should have a title')
previous_line = line
+21 -48
View File
@@ -1006,38 +1006,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using copy_scratch_value_t = std::pair<typename object_t::key_type, basic_json>;
using copy_scratch_t = std::vector<copy_scratch_value_t, AllocatorType<copy_scratch_value_t>>;
/// @brief tag selecting the constructor below; used only to build the
/// elements of a deep copy (@ref copy_array_level, @ref copy_object_level)
struct copy_construct_tag {};
public:
/*!
@brief construct a null value whose base class - and, with @ref
JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src
Copy-constructing @ref json_base_class_t here, rather than default-
constructing the element and assigning its base class afterwards, means
that copying a @ref basic_json only ever requires a copy-constructible
base class, and never a move-assignable one as well.
@note this constructor has to be public: @ref copy_array_level and
@ref copy_object_level reach it through @ref array_t's or @ref
object_t's own emplace_back(), which constructs the element from
outside @ref basic_json and so cannot call a private constructor.
@ref copy_construct_tag is private, though, and nothing in the
public interface hands out a value of it, so outside code can still
never name it to call this constructor itself.
*/
basic_json(copy_construct_tag /*unused*/, const basic_json& src)
: json_base_class_t(src)
#if JSON_DIAGNOSTIC_POSITIONS
, start_position(src.start_position)
, end_position(src.end_position)
#endif
/// @brief copy everything of @a src into @a dst but its type and value
static void copy_metadata(const basic_json& src, basic_json& dst)
{
}
// a custom base class is only required to be copy-constructible and
// move-assignable, so the copy has to go through a temporary
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
private:
#if JSON_DIAGNOSTIC_POSITIONS
dst.start_position = src.start_position;
dst.end_position = src.end_position;
#endif
}
/*!
@brief copy the value of @a src into @a dst, which must not be structured
@@ -1101,11 +1081,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
/*!
@brief finish the copy @a dst of @a src that a @ref copy_construct_tag
constructor started, other than the children of an object or array
@brief copy everything of @a src into the null value @a dst but the children
@a dst already has @a src's base class and, with @ref
JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing.
Objects and arrays are not copied here; they are appended to @a worklist to
be created later by @ref copy_iteratively. Until that happens, @a dst remains
a null value, so that a partially built copy can be destroyed at any point
@@ -1113,6 +1090,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
*/
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
{
copy_metadata(src, dst);
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
{
// defer: dst stays a null value until its container exists
@@ -1133,17 +1112,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{
const array_t& src_array = *src.m_data.m_value.array;
// create all elements up front: growing the array afterwards could
// invalidate the pointers that are handed to the worklist; resize()
// rather than the fill constructor, because not every array type
// provides the latter (e.g., ones without a matching allocator-aware
// fill constructor)
dst.m_data.m_value.array = create<array_t>();
// create every element - its base class already copy-constructed from
// its counterpart in src, via the copy_construct_tag constructor -
// before any of their addresses are handed to worklist below: growing
// the array while that is going on could reallocate it and invalidate
// addresses taken from an earlier iteration
for (const auto& src_element : src_array)
{
dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element);
}
dst.m_data.m_value.array->resize(src_array.size());
auto dst_it = dst.m_data.m_value.array->begin();
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
@@ -1161,14 +1136,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// build the complete key skeleton and hand it to the object's range
// constructor: adding the keys one by one would be quadratic for object
// types that are backed by a vector, such as nlohmann::ordered_map; each
// value's base class is already copy-constructed from its counterpart
// in src, via the copy_construct_tag constructor
// types that are backed by a vector, such as nlohmann::ordered_map
scratch.clear();
scratch.reserve(src_object.size());
for (const auto& element : src_object)
{
scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second));
scratch.emplace_back(element.first, basic_json());
}
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
+21 -48
View File
@@ -27087,38 +27087,18 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
using copy_scratch_value_t = std::pair<typename object_t::key_type, basic_json>;
using copy_scratch_t = std::vector<copy_scratch_value_t, AllocatorType<copy_scratch_value_t>>;
/// @brief tag selecting the constructor below; used only to build the
/// elements of a deep copy (@ref copy_array_level, @ref copy_object_level)
struct copy_construct_tag {};
public:
/*!
@brief construct a null value whose base class - and, with @ref
JSON_DIAGNOSTIC_POSITIONS, positions - are copied from @a src
Copy-constructing @ref json_base_class_t here, rather than default-
constructing the element and assigning its base class afterwards, means
that copying a @ref basic_json only ever requires a copy-constructible
base class, and never a move-assignable one as well.
@note this constructor has to be public: @ref copy_array_level and
@ref copy_object_level reach it through @ref array_t's or @ref
object_t's own emplace_back(), which constructs the element from
outside @ref basic_json and so cannot call a private constructor.
@ref copy_construct_tag is private, though, and nothing in the
public interface hands out a value of it, so outside code can still
never name it to call this constructor itself.
*/
basic_json(copy_construct_tag /*unused*/, const basic_json& src)
: json_base_class_t(src)
#if JSON_DIAGNOSTIC_POSITIONS
, start_position(src.start_position)
, end_position(src.end_position)
#endif
/// @brief copy everything of @a src into @a dst but its type and value
static void copy_metadata(const basic_json& src, basic_json& dst)
{
}
// a custom base class is only required to be copy-constructible and
// move-assignable, so the copy has to go through a temporary
static_cast<json_base_class_t&>(dst) = json_base_class_t(static_cast<const json_base_class_t&>(src));
private:
#if JSON_DIAGNOSTIC_POSITIONS
dst.start_position = src.start_position;
dst.end_position = src.end_position;
#endif
}
/*!
@brief copy the value of @a src into @a dst, which must not be structured
@@ -27182,11 +27162,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
}
/*!
@brief finish the copy @a dst of @a src that a @ref copy_construct_tag
constructor started, other than the children of an object or array
@brief copy everything of @a src into the null value @a dst but the children
@a dst already has @a src's base class and, with @ref
JSON_DIAGNOSTIC_POSITIONS, positions; only its value is still missing.
Objects and arrays are not copied here; they are appended to @a worklist to
be created later by @ref copy_iteratively. Until that happens, @a dst remains
a null value, so that a partially built copy can be destroyed at any point
@@ -27194,6 +27171,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
*/
static void copy_shallow(const basic_json& src, basic_json& dst, copy_worklist_t& worklist)
{
copy_metadata(src, dst);
if (src.m_data.m_type == value_t::object || src.m_data.m_type == value_t::array)
{
// defer: dst stays a null value until its container exists
@@ -27214,17 +27193,13 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
{
const array_t& src_array = *src.m_data.m_value.array;
// create all elements up front: growing the array afterwards could
// invalidate the pointers that are handed to the worklist; resize()
// rather than the fill constructor, because not every array type
// provides the latter (e.g., ones without a matching allocator-aware
// fill constructor)
dst.m_data.m_value.array = create<array_t>();
// create every element - its base class already copy-constructed from
// its counterpart in src, via the copy_construct_tag constructor -
// before any of their addresses are handed to worklist below: growing
// the array while that is going on could reallocate it and invalidate
// addresses taken from an earlier iteration
for (const auto& src_element : src_array)
{
dst.m_data.m_value.array->emplace_back(copy_construct_tag{}, src_element);
}
dst.m_data.m_value.array->resize(src_array.size());
auto dst_it = dst.m_data.m_value.array->begin();
for (auto src_it = src_array.cbegin(); src_it != src_array.cend(); ++src_it, ++dst_it)
@@ -27242,14 +27217,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
// build the complete key skeleton and hand it to the object's range
// constructor: adding the keys one by one would be quadratic for object
// types that are backed by a vector, such as nlohmann::ordered_map; each
// value's base class is already copy-constructed from its counterpart
// in src, via the copy_construct_tag constructor
// types that are backed by a vector, such as nlohmann::ordered_map
scratch.clear();
scratch.reserve(src_object.size());
for (const auto& element : src_object)
{
scratch.emplace_back(element.first, basic_json(copy_construct_tag{}, element.second));
scratch.emplace_back(element.first, basic_json());
}
dst.m_data.m_value.object = create<object_t>(std::make_move_iterator(scratch.begin()),
-70
View File
@@ -333,73 +333,3 @@ TEST_CASE("JSON Visit Node")
);
CHECK(expected.empty());
}
// A custom base class with a const member: copy-constructible (initializing a
// const member works fine), but not copy-/move-assignable (assigning one does
// not). Used to check that copy construction never requires more than that.
struct const_member_base
{
const int id = 7; // NOLINT(misc-non-private-member-variables-in-classes)
};
using json_with_const_base = nlohmann::basic_json <
std::map,
std::vector,
std::string,
bool,
std::int64_t,
std::uint64_t,
double,
std::allocator,
nlohmann::adl_serializer,
std::vector<std::uint8_t>,
const_member_base
>;
// build an array nested @a depth levels deep, with the innermost value 1;
// every level is constructed (never assigned), since const_member_base does
// not support assignment
static json_with_const_base make_nested_array(std::size_t depth)
{
if (depth == 0)
{
return json_with_const_base(1);
}
return json_with_const_base::array({make_nested_array(depth - 1)});
}
TEST_CASE("Regression test for issue #5674 - copy construction must not require an assignable base class")
{
SECTION("depth 0")
{
// as in the original bug report: copy construction only, no assignment
const json_with_const_base j = {1, 2};
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
CHECK(copy.size() == 2);
CHECK(copy.id == 7);
}
SECTION("nested deeper than the copy constructor's descent bound")
{
// beyond nesting_depth_limit() (128) levels, the copy constructor
// copies without the call stack (copy_iteratively / copy_array_level),
// which used to assign the base class of every element it created
const std::size_t depth = 300;
const json_with_const_base j = make_nested_array(depth);
const json_with_const_base copy = j; // NOLINT(performance-unnecessary-copy-initialization)
const json_with_const_base* c = &copy;
for (std::size_t level = 0; level <= depth; ++level)
{
CAPTURE(level)
REQUIRE(c->id == 7);
if (level < depth)
{
c = &c->at(0);
}
}
CHECK(*c == 1);
}
}