diff --git a/docs/mkdocs/docs/api/basic_json/operator+=.md b/docs/mkdocs/docs/api/basic_json/operator+=.md index 73dc0e260..4baa2d11a 100644 --- a/docs/mkdocs/docs/api/basic_json/operator+=.md +++ b/docs/mkdocs/docs/api/basic_json/operator+=.md @@ -27,6 +27,16 @@ reference operator+=(initializer_list_t init); `init` is converted into an object element and added using `operator+=(const typename object_t::value_type&)`. Otherwise, `init` is converted to a JSON value and added using `operator+=(basic_json&&)`. + !!! note "Single braced JSON value" + + For a call such as `#!cpp arr += {obj}`, where the initializer list contains a single JSON value `obj`, + compilers may disagree unless + [`JSON_BRACE_INIT_COPY_SEMANTICS`](../macros/json_brace_init_copy_semantics.md) is defined to `1`: GCC, for + example, selects this overload and appends a single-element array `[obj]`, whereas Clang selects the + `basic_json` overload and appends a copy of `obj`. With the macro, GCC and Clang both append a copy of `obj`. + To get a portable result without the macro, write `#!cpp arr += obj` to append a copy, or + `#!cpp arr += json::array({obj})` to append a single-element array. + ## Iterator invalidation For all cases where an element is added to an **array**, a reallocation can happen, in which case all iterators (including diff --git a/docs/mkdocs/docs/api/basic_json/push_back.md b/docs/mkdocs/docs/api/basic_json/push_back.md index 4bc885277..16e23b344 100644 --- a/docs/mkdocs/docs/api/basic_json/push_back.md +++ b/docs/mkdocs/docs/api/basic_json/push_back.md @@ -27,6 +27,16 @@ void push_back(initializer_list_t init); `init` is converted into an object element and added using `push_back(const typename object_t::value_type&)`. Otherwise, `init` is converted to a JSON value and added using `push_back(basic_json&&)`. + !!! note "Single braced JSON value" + + For a call such as `#!cpp arr.push_back({obj})`, where the initializer list contains a single JSON value `obj`, + compilers may disagree unless + [`JSON_BRACE_INIT_COPY_SEMANTICS`](../macros/json_brace_init_copy_semantics.md) is defined to `1`: GCC, for + example, selects this overload and appends a single-element array `[obj]`, whereas Clang selects the + `basic_json` overload and appends a copy of `obj`. With the macro, GCC and Clang both append a copy of `obj`. + To get a portable result without the macro, write `#!cpp arr.push_back(obj)` to append a copy, or + `#!cpp arr.push_back(json::array({obj}))` to append a single-element array. + ## Iterator invalidation For all cases where an element is added to an **array**, a reallocation can happen, in which case all iterators (including diff --git a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md index 796b20c6b..daa24ec87 100644 --- a/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md +++ b/docs/mkdocs/docs/api/macros/json_brace_init_copy_semantics.md @@ -27,9 +27,13 @@ The default value is `0` (disabled — existing behavior is preserved). json j{obj}; ``` - creates a single-element **array** `[{"key":"value"}]` instead of a copy of `obj`. This behavior is - compiler-dependent for older compilers (GCC wrapped, Clang did not), but starting from Clang 20, both compilers - behave the same way. + creates a single-element **array** `[{"key":"value"}]` instead of a copy of `obj`. For this constructor, the + behavior was compiler-dependent for older compilers (GCC wrapped, Clang did not), but starting from Clang 20, both + compilers behave the same way. + + The calls `#!cpp arr.push_back({obj})` and `#!cpp arr += {obj}` are still compiler-dependent without the macro, + even with Clang 20 and later: GCC converts `{obj}` to a single-element array and appends that array, whereas Clang + appends a copy of `obj`. With the macro defined to `1`, both compilers append a copy of `obj`. Enabling this macro opts into copy/move semantics for this case (see [#5074](https://github.com/nlohmann/json/issues/5074)). diff --git a/tests/src/unit-brace-init-copy-semantics.cpp b/tests/src/unit-brace-init-copy-semantics.cpp index e3406758b..7faf58e5f 100644 --- a/tests/src/unit-brace-init-copy-semantics.cpp +++ b/tests/src/unit-brace-init-copy-semantics.cpp @@ -94,6 +94,37 @@ TEST_CASE("JSON_BRACE_INIT_COPY_SEMANTICS") CHECK(j3 == pair_shaped); } + SECTION("push_back and operator+= with a single braced JSON value") + { + // with the macro, the result does not depend on the compiler; + // without it, GCC wraps a JSON value and Clang does not (a literal + // such as {1} is wrapped by both) + json const obj = {{"key", "value"}}; + json const arr = {1, 2}; + + json a1 = json::array(); + a1.push_back({obj}); + CHECK(a1 == json::array({obj})); + + json a2 = json::array(); + a2 += {obj}; + CHECK(a2 == json::array({obj})); + + json b1 = json::array(); + b1.push_back({arr}); + CHECK(b1 == json::array({arr})); + + json b2 = json::array(); + b2 += {arr}; + CHECK(b2 == json::array({arr})); + + // a literal is copied as well + json c = json::array(); + c.push_back({1}); + c += {2}; + CHECK(c == json::array({1, 2})); + } + SECTION("what the macro does not change") { // lists with more than one element are unaffected