Skip to content

Commit 363a2ba

Browse files
committed
fix: encode JSON_BRACE_INIT_COPY_SEMANTICS in ABI namespace tag
Move the JSON_BRACE_INIT_COPY_SEMANTICS default from macro_scope.hpp into abi_macros.hpp alongside the other ABI-affecting macros, and add an inline namespace tag (_bics) so that TUs compiled with different settings produce a link error instead of silent ODR-undefined behaviour. With the macro enabled the inline namespace becomes json_abi_bics_v3_12_0; without it the namespace stays json_abi_v3_12_0. Also correct the FAQ wording: the default behaviour (without the macro) is compiler-dependent, not unconditionally array-wrapping. Clang < 20 performed copy/move while GCC and Clang 20+ wrap in an array.
1 parent b15dde2 commit 363a2ba

4 files changed

Lines changed: 111 additions & 87 deletions

File tree

docs/mkdocs/docs/home/faq.md

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,11 @@ json obj = {{"key", "value"}};
5656
json j{obj}; // -> {"key":"value"} (copy, not array)
5757
```
5858
59-
Without the macro (default behavior), `json j{obj}` creates `[{"key":"value"}]`. This opt-in macro fixes issue #5074 while preserving backwards compatibility for existing code.
59+
Without the macro, the behavior is compiler-dependent: GCC and Clang 20+ wrap `obj` in a single-element array (`[{"key":"value"}]`), while earlier Clang versions copy it (`{"key":"value"}`). Defining `JSON_BRACE_INIT_COPY_SEMANTICS` makes the copy/move behavior explicit and portable across all compilers.
60+
61+
!!! warning
62+
63+
Mixing translation units compiled with and without `JSON_BRACE_INIT_COPY_SEMANTICS` in the same program is an ODR violation and leads to undefined behavior. Ensure the macro is defined consistently across your entire build.
6064
6165
## Limitations
6266

include/nlohmann/detail/abi_macros.hpp

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -52,20 +52,31 @@
5252
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
5353
#endif
5454

55+
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
56+
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
57+
#endif
58+
59+
#if JSON_BRACE_INIT_COPY_SEMANTICS
60+
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
61+
#else
62+
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
63+
#endif
64+
5565
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
5666
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
5767
#endif
5868

5969
// Construct the namespace ABI tags component
60-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
61-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
62-
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
63-
64-
#define NLOHMANN_JSON_ABI_TAGS \
65-
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
66-
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
67-
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
68-
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
70+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
71+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
72+
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
73+
74+
#define NLOHMANN_JSON_ABI_TAGS \
75+
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
76+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
77+
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
78+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
79+
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
6980

7081
// Construct the namespace version component
7182
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \

include/nlohmann/detail/macro_scope.hpp

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -600,6 +600,5 @@
600600
#define JSON_USE_GLOBAL_UDLS 1
601601
#endif
602602

603-
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
604-
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
605-
#endif
603+
// JSON_BRACE_INIT_COPY_SEMANTICS is defined and defaulted in abi_macros.hpp
604+
// because it affects the inline namespace ABI tag.

single_include/nlohmann/json.hpp

Lines changed: 84 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -99,20 +99,31 @@
9999
#define NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON
100100
#endif
101101

102+
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
103+
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
104+
#endif
105+
106+
#if JSON_BRACE_INIT_COPY_SEMANTICS
107+
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS _bics
108+
#else
109+
#define NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS
110+
#endif
111+
102112
#ifndef NLOHMANN_JSON_NAMESPACE_NO_VERSION
103113
#define NLOHMANN_JSON_NAMESPACE_NO_VERSION 0
104114
#endif
105115

106116
// Construct the namespace ABI tags component
107-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c) json_abi ## a ## b ## c
108-
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c) \
109-
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c)
117+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d) json_abi ## a ## b ## c ## d
118+
#define NLOHMANN_JSON_ABI_TAGS_CONCAT(a, b, c, d) \
119+
NLOHMANN_JSON_ABI_TAGS_CONCAT_EX(a, b, c, d)
110120

111-
#define NLOHMANN_JSON_ABI_TAGS \
112-
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
113-
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
114-
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
115-
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS)
121+
#define NLOHMANN_JSON_ABI_TAGS \
122+
NLOHMANN_JSON_ABI_TAGS_CONCAT( \
123+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTICS, \
124+
NLOHMANN_JSON_ABI_TAG_LEGACY_DISCARDED_VALUE_COMPARISON, \
125+
NLOHMANN_JSON_ABI_TAG_DIAGNOSTIC_POSITIONS, \
126+
NLOHMANN_JSON_ABI_TAG_BRACE_INIT_COPY_SEMANTICS)
116127

117128
// Construct the namespace version component
118129
#define NLOHMANN_JSON_NAMESPACE_VERSION_CONCAT_EX(major, minor, patch) \
@@ -2964,9 +2975,8 @@ JSON_HEDLEY_DIAGNOSTIC_POP
29642975
#define JSON_USE_GLOBAL_UDLS 1
29652976
#endif
29662977

2967-
#ifndef JSON_BRACE_INIT_COPY_SEMANTICS
2968-
#define JSON_BRACE_INIT_COPY_SEMANTICS 0
2969-
#endif
2978+
// JSON_BRACE_INIT_COPY_SEMANTICS is defined and defaulted in abi_macros.hpp
2979+
// because it affects the inline namespace ABI tag.
29702980

29712981
#if JSON_HAS_THREE_WAY_COMPARISON
29722982
#include <compare> // partial_ordering
@@ -3503,71 +3513,71 @@ NLOHMANN_JSON_NAMESPACE_END
35033513
// SPDX-License-Identifier: MIT
35043514

35053515
#ifndef INCLUDE_NLOHMANN_JSON_FWD_HPP_
3506-
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
3516+
#define INCLUDE_NLOHMANN_JSON_FWD_HPP_
35073517

3508-
#include <cstdint> // int64_t, uint64_t
3509-
#include <map> // map
3510-
#include <memory> // allocator
3511-
#include <string> // string
3512-
#include <vector> // vector
3518+
#include <cstdint> // int64_t, uint64_t
3519+
#include <map> // map
3520+
#include <memory> // allocator
3521+
#include <string> // string
3522+
#include <vector> // vector
35133523

3514-
// #include <nlohmann/detail/abi_macros.hpp>
3524+
// #include <nlohmann/detail/abi_macros.hpp>
35153525

35163526

3517-
/*!
3518-
@brief namespace for Niels Lohmann
3519-
@see https://github.com/nlohmann
3520-
@since version 1.0.0
3521-
*/
3522-
NLOHMANN_JSON_NAMESPACE_BEGIN
3527+
/*!
3528+
@brief namespace for Niels Lohmann
3529+
@see https://github.com/nlohmann
3530+
@since version 1.0.0
3531+
*/
3532+
NLOHMANN_JSON_NAMESPACE_BEGIN
35233533

3524-
/*!
3525-
@brief default JSONSerializer template argument
3534+
/*!
3535+
@brief default JSONSerializer template argument
35263536

3527-
This serializer ignores the template arguments and uses ADL
3528-
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
3529-
for serialization.
3530-
*/
3531-
template<typename T = void, typename SFINAE = void>
3532-
struct adl_serializer;
3533-
3534-
/// a class to store JSON values
3535-
/// @sa https://json.nlohmann.me/api/basic_json/
3536-
template<template<typename U, typename V, typename... Args> class ObjectType =
3537-
std::map,
3538-
template<typename U, typename... Args> class ArrayType = std::vector,
3539-
class StringType = std::string, class BooleanType = bool,
3540-
class NumberIntegerType = std::int64_t,
3541-
class NumberUnsignedType = std::uint64_t,
3542-
class NumberFloatType = double,
3543-
template<typename U> class AllocatorType = std::allocator,
3544-
template<typename T, typename SFINAE = void> class JSONSerializer =
3545-
adl_serializer,
3546-
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
3547-
class CustomBaseClass = void>
3548-
class basic_json;
3549-
3550-
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
3551-
/// @sa https://json.nlohmann.me/api/json_pointer/
3552-
template<typename RefStringType>
3553-
class json_pointer;
3537+
This serializer ignores the template arguments and uses ADL
3538+
([argument-dependent lookup](https://en.cppreference.com/w/cpp/language/adl))
3539+
for serialization.
3540+
*/
3541+
template<typename T = void, typename SFINAE = void>
3542+
struct adl_serializer;
3543+
3544+
/// a class to store JSON values
3545+
/// @sa https://json.nlohmann.me/api/basic_json/
3546+
template<template<typename U, typename V, typename... Args> class ObjectType =
3547+
std::map,
3548+
template<typename U, typename... Args> class ArrayType = std::vector,
3549+
class StringType = std::string, class BooleanType = bool,
3550+
class NumberIntegerType = std::int64_t,
3551+
class NumberUnsignedType = std::uint64_t,
3552+
class NumberFloatType = double,
3553+
template<typename U> class AllocatorType = std::allocator,
3554+
template<typename T, typename SFINAE = void> class JSONSerializer =
3555+
adl_serializer,
3556+
class BinaryType = std::vector<std::uint8_t>, // cppcheck-suppress syntaxError
3557+
class CustomBaseClass = void>
3558+
class basic_json;
35543559

3555-
/*!
3556-
@brief default specialization
3557-
@sa https://json.nlohmann.me/api/json/
3558-
*/
3559-
using json = basic_json<>;
3560+
/// @brief JSON Pointer defines a string syntax for identifying a specific value within a JSON document
3561+
/// @sa https://json.nlohmann.me/api/json_pointer/
3562+
template<typename RefStringType>
3563+
class json_pointer;
35603564

3561-
/// @brief a minimal map-like container that preserves insertion order
3562-
/// @sa https://json.nlohmann.me/api/ordered_map/
3563-
template<class Key, class T, class IgnoredLess, class Allocator>
3564-
struct ordered_map;
3565+
/*!
3566+
@brief default specialization
3567+
@sa https://json.nlohmann.me/api/json/
3568+
*/
3569+
using json = basic_json<>;
3570+
3571+
/// @brief a minimal map-like container that preserves insertion order
3572+
/// @sa https://json.nlohmann.me/api/ordered_map/
3573+
template<class Key, class T, class IgnoredLess, class Allocator>
3574+
struct ordered_map;
35653575

3566-
/// @brief specialization that maintains the insertion order of object keys
3567-
/// @sa https://json.nlohmann.me/api/ordered_json/
3568-
using ordered_json = basic_json<nlohmann::ordered_map>;
3576+
/// @brief specialization that maintains the insertion order of object keys
3577+
/// @sa https://json.nlohmann.me/api/ordered_json/
3578+
using ordered_json = basic_json<nlohmann::ordered_map>;
35693579

3570-
NLOHMANN_JSON_NAMESPACE_END
3580+
NLOHMANN_JSON_NAMESPACE_END
35713581

35723582
#endif // INCLUDE_NLOHMANN_JSON_FWD_HPP_
35733583

@@ -5431,7 +5441,7 @@ NLOHMANN_JSON_NAMESPACE_END
54315441

54325442

54335443
// #include <nlohmann/detail/macro_scope.hpp>
5434-
// JSON_HAS_CPP_17
5444+
// JSON_HAS_CPP_17
54355445
#ifdef JSON_HAS_CPP_17
54365446
#include <optional> // optional
54375447
#endif
@@ -20259,10 +20269,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
2025920269
const bool allow_exceptions = true,
2026020270
const bool ignore_comments = false,
2026120271
const bool ignore_trailing_commas = false
20262-
)
20272+
)
2026320273
{
2026420274
return ::nlohmann::detail::parser<basic_json, InputAdapterType>(std::move(adapter),
20265-
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
20275+
std::move(cb), allow_exceptions, ignore_comments, ignore_trailing_commas);
2026620276
}
2026720277

2026820278
private:
@@ -20960,8 +20970,8 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
2096020970
detail::enable_if_t <
2096120971
!detail::is_basic_json<U>::value && detail::is_compatible_type<basic_json_t, U>::value, int > = 0 >
2096220972
basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape)
20963-
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
20964-
std::forward<CompatibleType>(val))))
20973+
JSONSerializer<U>::to_json(std::declval<basic_json_t&>(),
20974+
std::forward<CompatibleType>(val))))
2096520975
{
2096620976
JSONSerializer<U>::to_json(*this, std::forward<CompatibleType>(val));
2096720977
set_parents();
@@ -21764,7 +21774,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
2176421774
detail::has_from_json<basic_json_t, ValueType>::value,
2176521775
int > = 0 >
2176621776
ValueType get_impl(detail::priority_tag<0> /*unused*/) const noexcept(noexcept(
21767-
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
21777+
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), std::declval<ValueType&>())))
2176821778
{
2176921779
auto ret = ValueType();
2177021780
JSONSerializer<ValueType>::from_json(*this, ret);
@@ -21806,7 +21816,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
2180621816
detail::has_non_default_from_json<basic_json_t, ValueType>::value,
2180721817
int > = 0 >
2180821818
ValueType get_impl(detail::priority_tag<1> /*unused*/) const noexcept(noexcept(
21809-
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
21819+
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>())))
2181021820
{
2181121821
return JSONSerializer<ValueType>::from_json(*this);
2181221822
}
@@ -21956,7 +21966,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec
2195621966
detail::has_from_json<basic_json_t, ValueType>::value,
2195721967
int > = 0 >
2195821968
ValueType & get_to(ValueType& v) const noexcept(noexcept(
21959-
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
21969+
JSONSerializer<ValueType>::from_json(std::declval<const basic_json_t&>(), v)))
2196021970
{
2196121971
JSONSerializer<ValueType>::from_json(*this, v);
2196221972
return v;

0 commit comments

Comments
 (0)