diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index d8c27c621..d8f15e3dd 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -100,7 +100,7 @@ jobs: container: ubuntu:focal strategy: matrix: - target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local] + target: [ci_cmake_flags, ci_test_diagnostics, ci_test_diagnostic_positions, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison, ci_test_noglobaludls, ci_test_disableenumserialization, ci_test_disabletuplereferenceconversion, ci_test_skiplibraryversioncheck, ci_test_simdutf, ci_test_strict_nul_handling, ci_test_no_thread_local] steps: - name: Install build-essential run: apt-get update ; apt-get install -y build-essential unzip wget git libssl-dev diff --git a/CMakeLists.txt b/CMakeLists.txt index 4c43c23ff..f0a6771dc 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -55,6 +55,7 @@ option(JSON_Diagnostic_Positions "Enable diagnostic positions." OFF) option(JSON_GlobalUDLs "Place user-defined string literals in the global namespace." ON) option(JSON_ImplicitConversions "Enable implicit conversions." ON) option(JSON_DisableEnumSerialization "Disable default integer enum serialization." OFF) +option(JSON_DisableTupleReferenceConversion "Disable conversion from a one-element tuple of a JSON reference." OFF) option(JSON_LegacyDiscardedValueComparison "Enable legacy discarded value comparison." OFF) option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) option(JSON_MultipleHeaders "Use non-amalgamated version of the library." ON) @@ -101,6 +102,10 @@ if (JSON_DisableEnumSerialization) message(STATUS "Enum integer serialization is disabled (JSON_DISABLE_ENUM_SERIALIZATION=1)") endif() +if (JSON_DisableTupleReferenceConversion) + message(STATUS "Tuple reference conversion is disabled (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1)") +endif() + if (JSON_LegacyDiscardedValueComparison) message(STATUS "Legacy discarded value comparison enabled (JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1)") endif() @@ -143,6 +148,7 @@ target_compile_definitions( $<$>:JSON_USE_GLOBAL_UDLS=0> $<$>:JSON_USE_IMPLICIT_CONVERSIONS=0> $<$:JSON_DISABLE_ENUM_SERIALIZATION=1> + $<$:JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1> $<$:JSON_DIAGNOSTICS=1> $<$:JSON_DIAGNOSTIC_POSITIONS=1> $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> diff --git a/cmake/ci.cmake b/cmake/ci.cmake index a001a05ae..27187f235 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -276,6 +276,20 @@ add_custom_target(ci_test_disableenumserialization COMMENT "Compile and test with enum serialization disabled" ) +############################################################################### +# Disable conversion from a one-element tuple of a JSON reference. +############################################################################### + +add_custom_target(ci_test_disabletuplereferenceconversion + COMMAND ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DJSON_BuildTests=ON -DJSON_FastTests=ON -DJSON_DisableTupleReferenceConversion=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion + COMMAND cd ${PROJECT_BINARY_DIR}/build_disabletuplereferenceconversion && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMENT "Compile and test with tuple reference conversion disabled" +) + ############################################################################### # Skip the multiple-inclusion library version check. ############################################################################### diff --git a/docs/docset/docSet.sql b/docs/docset/docSet.sql index 3a97e405f..6ec90dcfc 100644 --- a/docs/docset/docSet.sql +++ b/docs/docset/docSet.sql @@ -210,6 +210,7 @@ INSERT INTO searchIndex(name, type, path) VALUES ('JSON_CATCH_USER', 'Macro', 'a INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTICS', 'Macro', 'api/macros/json_diagnostics/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DIAGNOSTIC_POSITIONS', 'Macro', 'api/macros/json_diagnostic_positions/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_ENUM_SERIALIZATION', 'Macro', 'api/macros/json_disable_enum_serialization/index.html'); +INSERT INTO searchIndex(name, type, path) VALUES ('JSON_DISABLE_TUPLE_REFERENCE_CONVERSION', 'Macro', 'api/macros/json_disable_tuple_reference_conversion/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_11', 'Macro', 'api/macros/json_has_cpp_11/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_14', 'Macro', 'api/macros/json_has_cpp_11/index.html'); INSERT INTO searchIndex(name, type, path) VALUES ('JSON_HAS_CPP_17', 'Macro', 'api/macros/json_has_cpp_11/index.html'); diff --git a/docs/mkdocs/docs/api/basic_json/basic_json.md b/docs/mkdocs/docs/api/basic_json/basic_json.md index 3a7a6eb8b..962f3e164 100644 --- a/docs/mkdocs/docs/api/basic_json/basic_json.md +++ b/docs/mkdocs/docs/api/basic_json/basic_json.md @@ -159,6 +159,8 @@ basic_json(basic_json&& other) noexcept; - `CompatibleType` is not `basic_json` (to avoid hijacking copy/move constructors), - `CompatibleType` is not a different `basic_json` type (i.e. with different template arguments) - `CompatibleType` is not a `basic_json` nested type (e.g., `json_pointer`, `iterator`, etc.) + - if [`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../macros/json_disable_tuple_reference_conversion.md) is defined + to `1`: `CompatibleType` is not a one-element `std::tuple` holding a reference to `basic_json` - `json_serializer` (with `U = uncvref_t`) has a `to_json(basic_json_t&, CompatibleType&&)` method diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 9d2636eb2..e917e0ae2 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -53,6 +53,7 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_BRACE_INIT_COPY_SEMANTICS**](json_brace_init_copy_semantics.md) - opt in to copy/move semantics for single-element brace initialization - [**JSON_DISABLE_ENUM_SERIALIZATION**](json_disable_enum_serialization.md) - switch off default serialization/deserialization functions for enums +- [**JSON_DISABLE_TUPLE_REFERENCE_CONVERSION**](json_disable_tuple_reference_conversion.md) - switch off conversion from a one-element tuple of a JSON reference - [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions ## Comparison behavior diff --git a/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md new file mode 100644 index 000000000..c759fd65c --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_disable_tuple_reference_conversion.md @@ -0,0 +1,114 @@ +# JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + +```cpp +#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION /* value */ +``` + +When defined to `1`, a `basic_json` value can no longer be constructed from a one-element `std::tuple` whose element is +a reference to that `basic_json` type, such as `std::tuple`, `std::tuple`, or `std::tuple`. +These are the tuples created by `std::forward_as_tuple(j)`. + +## Default definition + +The default value is `0` (disabled — existing behavior is preserved). + +```cpp +#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0 +``` + +## Notes + +!!! note "Background" + + By default, `basic_json` can be constructed from any `std::tuple` whose elements can be converted to JSON; the result + is an array. This includes `std::tuple`, which becomes a one-element array. + + `std::tuple` only converts another tuple element by element if its element type cannot be constructed from the whole + source tuple. Because `json` *can* be constructed from `std::tuple`, `std::tuple` instead converts the whole + tuple into a single `json` value. This has two surprising effects: + + ```cpp + json j = true; + + // rejected by some standard libraries (e.g., libc++); with others, the + // reference binds to a temporary that is destroyed right away + std::tuple t1(std::forward_as_tuple(j)); + + // compiles, but std::get<0>(t2) is [true], not true + std::tuple t2(std::forward_as_tuple(j)); + ``` + + Enabling this macro removes the conversion, so both tuples are converted element by element: `std::get<0>(t1)` + refers to `j`, and `std::get<0>(t2)` is a copy of `j` (see [#2226](https://github.com/nlohmann/json/issues/2226)). + +!!! warning "Opt-in only" + + This macro must be defined **before** including ``. Defining it after the include has no effect. + +!!! note "Affected conversions" + + Only one-element tuples holding a reference to the **same** `basic_json` type are affected. Constructing a JSON value + from them no longer compiles: + + ```cpp + json j = true; + json a = std::forward_as_tuple(j); // error with the macro enabled + json b = json::array({j}); // use this instead: [true] + ``` + + Tuples holding a JSON value (`std::make_tuple(j)`), tuples with more than one element, and tuples holding references + to other types (including other `basic_json` specializations) are converted to arrays as before. + +!!! hint "CMake option" + + This behavior can also be controlled with the CMake option + [`JSON_DisableTupleReferenceConversion`](../../integration/cmake.md#json_disabletuplereferenceconversion) + (`OFF` by default) which defines `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` accordingly. + +## Examples + +??? example "Default behavior (macro not defined)" + + ```cpp + #include + + using json = nlohmann::json; + + int main() + { + json j = true; + + std::tuple t(std::forward_as_tuple(j)); + // std::get<0>(t) is [true] -- the whole tuple was converted + } + ``` + +??? example "Conversion disabled (macro defined to 1)" + + ```cpp + #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1 + #include + + using json = nlohmann::json; + + int main() + { + json j = true; + + std::tuple t(std::forward_as_tuple(j)); + // std::get<0>(t) is true -- a copy of j + + std::tuple r(std::forward_as_tuple(j)); + // std::get<0>(r) refers to j + } + ``` + +## See also + +- [**basic_json(CompatibleType&&)**](../basic_json/basic_json.md) - the affected constructor +- [:simple-cmake: JSON_DisableTupleReferenceConversion](../../integration/cmake.md#json_disabletuplereferenceconversion) - + CMake option to control the macro + +## Version history + +- Added in version 3.13.0. diff --git a/docs/mkdocs/docs/features/macros.md b/docs/mkdocs/docs/features/macros.md index e4a628c3e..a4479712f 100644 --- a/docs/mkdocs/docs/features/macros.md +++ b/docs/mkdocs/docs/features/macros.md @@ -83,6 +83,13 @@ When defined, default parse and serialize functions for enums are excluded and h See [full documentation of `JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). +## `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION` + +When defined to `1`, a JSON value can no longer be created from a one-element `std::tuple` holding a reference to a JSON +value, such as the result of `std::forward_as_tuple(j)`. This lets `std::tuple` convert such tuples element-wise. + +See [full documentation of `JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). + ## `JSON_NO_AUTOMATIC_UDLS` When defined, `` does not include `` with the user-defined string literals diff --git a/docs/mkdocs/docs/integration/cmake.md b/docs/mkdocs/docs/integration/cmake.md index 71512cbd5..4160584d5 100644 --- a/docs/mkdocs/docs/integration/cmake.md +++ b/docs/mkdocs/docs/integration/cmake.md @@ -169,6 +169,12 @@ Enable position diagnostics by defining macro [`JSON_DIAGNOSTIC_POSITIONS`](../a Disable default `enum` serialization by defining the macro [`JSON_DISABLE_ENUM_SERIALIZATION`](../api/macros/json_disable_enum_serialization.md). This option is `OFF` by default. +### `JSON_DisableTupleReferenceConversion` + +Disable the conversion from a one-element `std::tuple` holding a reference to a JSON value by defining the macro +[`JSON_DISABLE_TUPLE_REFERENCE_CONVERSION`](../api/macros/json_disable_tuple_reference_conversion.md). This option is +`OFF` by default. + ### `JSON_FastTests` Skip expensive/slow test suites. This option is `OFF` by default. Depends on `JSON_BuildTests`. diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index 21fad832a..b31fb1734 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -287,6 +287,7 @@ nav: - 'JSON_DIAGNOSTICS': api/macros/json_diagnostics.md - 'JSON_DIAGNOSTIC_POSITIONS': api/macros/json_diagnostic_positions.md - 'JSON_DISABLE_ENUM_SERIALIZATION': api/macros/json_disable_enum_serialization.md + - 'JSON_DISABLE_TUPLE_REFERENCE_CONVERSION': api/macros/json_disable_tuple_reference_conversion.md - 'JSON_HAS_CPP_11, JSON_HAS_CPP_14, JSON_HAS_CPP_17, JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM, JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md - 'JSON_HAS_RANGES': api/macros/json_has_ranges.md diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index fb48e4f82..92574c8ce 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -471,11 +471,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } -#if JSON_BRACE_INIT_COPY_SEMANTICS -// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its -// element instead of wrapping it, which would serialize std::tuple{5} as 5 -// rather than [5]. Build what the default deduction builds instead: an object -// if the element is a [string, value] pair, a one-element array otherwise. +// A one-element braced list does not reliably wrap its element: with +// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize +// std::tuple{5} as 5 rather than [5], and some compilers (e.g., Apple clang +// 15 and 16) copy an element that is itself a basic_json even without it, so +// std::tuple{true} became true rather than [true]. Build what the default +// deduction builds instead: an object if the element is a [string, value] pair, +// a one-element array otherwise. template inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/) { @@ -493,7 +495,6 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = BasicJsonType::array({std::move(element)}); } } -#endif template inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index 0d524c710..2d0fccf18 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -915,3 +915,7 @@ void templated_json_throw(ExceptionType exception) #ifndef JSON_DISABLE_ENUM_SERIALIZATION #define JSON_DISABLE_ENUM_SERIALIZATION 0 #endif + +#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0 +#endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index d2675d3f1..d6aa831d7 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -25,6 +25,7 @@ #undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #undef JSON_DISABLE_ENUM_SERIALIZATION +#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION #ifndef JSON_TEST_KEEP_MACROS #undef JSON_CATCH diff --git a/include/nlohmann/detail/meta/type_traits.hpp b/include/nlohmann/detail/meta/type_traits.hpp index 36573bf6f..96b70a774 100644 --- a/include/nlohmann/detail/meta/type_traits.hpp +++ b/include/nlohmann/detail/meta/type_traits.hpp @@ -636,6 +636,18 @@ template struct is_compatible_type : is_compatible_type_impl {}; +// a one-element std::tuple holding a reference to BasicJsonType, as created by +// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION +template +struct is_basic_json_reference_tuple : std::false_type {}; + +template +struct is_basic_json_reference_tuple> +{ + static constexpr bool value = + std::is_reference::value && std::is_same, BasicJsonType>::value; +}; + template struct is_compatible_binary_type { diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index 78c4e4f62..a82c1eb11 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1596,7 +1596,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < typename CompatibleType, typename U = detail::uncvref_t, detail::enable_if_t < - !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + !detail::is_basic_json::value && detail::is_compatible_type::value +#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + // see https://github.com/nlohmann/json/issues/2226 + && !detail::is_basic_json_reference_tuple::value +#endif + , int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) JSONSerializer::to_json(std::declval(), std::forward(val)))) diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5ef598150..d6d2ef347 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -3329,6 +3329,10 @@ void templated_json_throw(ExceptionType exception) #define JSON_DISABLE_ENUM_SERIALIZATION 0 #endif +#ifndef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + #define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 0 +#endif + #if JSON_HAS_THREE_WAY_COMPARISON #include // partial_ordering #endif @@ -4644,6 +4648,18 @@ template struct is_compatible_type : is_compatible_type_impl {}; +// a one-element std::tuple holding a reference to BasicJsonType, as created by +// std::forward_as_tuple(j); see JSON_DISABLE_TUPLE_REFERENCE_CONVERSION +template +struct is_basic_json_reference_tuple : std::false_type {}; + +template +struct is_basic_json_reference_tuple> +{ + static constexpr bool value = + std::is_reference::value && std::is_same, BasicJsonType>::value; +}; + template struct is_compatible_binary_type { @@ -7002,11 +7018,13 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = { std::get(t)... }; } -#if JSON_BRACE_INIT_COPY_SEMANTICS -// JSON_BRACE_INIT_COPY_SEMANTICS makes a one-element braced list copy its -// element instead of wrapping it, which would serialize std::tuple{5} as 5 -// rather than [5]. Build what the default deduction builds instead: an object -// if the element is a [string, value] pair, a one-element array otherwise. +// A one-element braced list does not reliably wrap its element: with +// JSON_BRACE_INIT_COPY_SEMANTICS it copies it, which would serialize +// std::tuple{5} as 5 rather than [5], and some compilers (e.g., Apple clang +// 15 and 16) copy an element that is itself a basic_json even without it, so +// std::tuple{true} became true rather than [true]. Build what the default +// deduction builds instead: an object if the element is a [string, value] pair, +// a one-element array otherwise. template inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence<0> /*unused*/) { @@ -7024,7 +7042,6 @@ inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence< j = BasicJsonType::array({std::move(element)}); } } -#endif template inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& /*unused*/, index_sequence<> /*unused*/) @@ -28523,7 +28540,12 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec template < typename CompatibleType, typename U = detail::uncvref_t, detail::enable_if_t < - !detail::is_basic_json::value && detail::is_compatible_type::value, int > = 0 > + !detail::is_basic_json::value && detail::is_compatible_type::value +#if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + // see https://github.com/nlohmann/json/issues/2226 + && !detail::is_basic_json_reference_tuple::value +#endif + , int > = 0 > basic_json(CompatibleType && val) noexcept(noexcept( // NOLINT(bugprone-forwarding-reference-overload,bugprone-exception-escape) JSONSerializer::to_json(std::declval(), std::forward(val)))) @@ -33572,6 +33594,7 @@ struct formatter // NOLINT(cert-dcl58-c #undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #undef JSON_DISABLE_ENUM_SERIALIZATION +#undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION #ifndef JSON_TEST_KEEP_MACROS #undef JSON_CATCH diff --git a/tests/src/unit-disable-tuple-reference-conversion.cpp b/tests/src/unit-disable-tuple-reference-conversion.cpp new file mode 100644 index 000000000..6d978620b --- /dev/null +++ b/tests/src/unit-disable-tuple-reference-conversion.cpp @@ -0,0 +1,93 @@ +// __ _____ _____ _____ +// __| | __| | | | JSON for Modern C++ (supporting code) +// | | |__ | | | | | | version 3.12.0 +// |_____|_____|_____|_|___| https://github.com/nlohmann/json +// +// SPDX-FileCopyrightText: 2013-2026 Niels Lohmann +// SPDX-License-Identifier: MIT + +#include "doctest_compatibility.h" + +// This file tests the opt-in JSON_DISABLE_TUPLE_REFERENCE_CONVERSION, so it +// defines the macro itself rather than relying on a -D flag, and runs in every +// build. +#ifdef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION + #undef JSON_DISABLE_TUPLE_REFERENCE_CONVERSION +#endif + +#define JSON_DISABLE_TUPLE_REFERENCE_CONVERSION 1 + +#include +using nlohmann::json; +using nlohmann::ordered_json; + +#include +#include +#include +#include + +// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json +// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION: +// the tuple constructors make them instantiate basic_json's conversion operator +// for libstdc++'s internal tuple bases, which fails hard +#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5) + #define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES +#endif + +TEST_CASE("JSON_DISABLE_TUPLE_REFERENCE_CONVERSION") +{ + SECTION("json is not constructible from a one-element tuple of a json reference") + { + CHECK_FALSE(std::is_constructible>::value); + CHECK_FALSE(std::is_constructible>::value); + CHECK_FALSE(std::is_constructible < json, std::tuple < json && >>::value); + CHECK_FALSE(std::is_constructible&>::value); + CHECK_FALSE(std::is_constructible>::value); + } + +#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES + SECTION("issue #2226 - tuple from tuple keeps the reference") + { + json j = true; + const std::tuple tup(std::forward_as_tuple(j)); + CHECK(&std::get<0>(tup) == &j); + } + + SECTION("tuple from tuple copies the element") + { + const json j = {{"key", "value"}}; + const std::tuple t1(std::forward_as_tuple(j)); + CHECK(std::get<0>(t1) == j); + + json j2 = "text"; + const std::tuple t2(std::forward_as_tuple(std::move(j2))); + CHECK(std::get<0>(t2) == "text"); + } +#endif + + SECTION("other tuple conversions are not affected") + { + const json j = true; + + // one-element tuple holding a json value + CHECK(json(std::make_tuple(j)) == json::array({true})); + + // tuples with more than one element, even when holding references + int i = 1; +#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES + CHECK(json(std::forward_as_tuple(i, j)) == json::array({1, true})); + CHECK(json(std::forward_as_tuple(j, j)) == json::array({true, true})); +#endif + + // one-element tuples holding references to other types + std::string s = "text"; + CHECK(json(std::forward_as_tuple(s)) == json::array({"text"})); + CHECK(json(std::forward_as_tuple(i)) == json::array({1})); + +#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES + // a reference to a different basic_json specialization + ordered_json oj = true; + CHECK(json(std::forward_as_tuple(oj)) == json::array({true})); +#endif + } +} diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index c466958bf..6446f5f8c 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -18,6 +18,19 @@ // for some reason including this after the json header leads to linker errors with VS 2017... #include +// skip tests if JSON_DISABLE_TUPLE_REFERENCE_CONVERSION=1 (#2226) +#if defined(JSON_DISABLE_TUPLE_REFERENCE_CONVERSION) && (JSON_DISABLE_TUPLE_REFERENCE_CONVERSION == 1) + #define SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION +#endif + +// clang before 4 and GCC before 5 cannot create a std::tuple of basic_json +// references at all, with or without JSON_DISABLE_TUPLE_REFERENCE_CONVERSION: +// the tuple constructors make them instantiate basic_json's conversion operator +// for libstdc++'s internal tuple bases, which fails hard +#if (defined(__clang__) && __clang_major__ < 4) || (!defined(__clang__) && defined(__GNUC__) && __GNUC__ < 5) + #define SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES +#endif + #define JSON_TESTS_PRIVATE #include using json = nlohmann::json; @@ -28,6 +41,7 @@ using ordered_json = nlohmann::ordered_json; #include #include +#include #include #include @@ -542,6 +556,20 @@ TEST_CASE("regression tests 2") ))); } +#ifndef SKIP_TESTS_FOR_TUPLE_REFERENCE_CONVERSION + SECTION("issue #2226 - std::tuple dangling reference - implicit conversion") + { + // by default, a one-element tuple holding a json reference converts to + // a one-element array; JSON_DISABLE_TUPLE_REFERENCE_CONVERSION removes + // this conversion (see unit-disable-tuple-reference-conversion.cpp) + const json j = true; + CHECK(std::is_constructible>::value); +#ifndef SKIP_TESTS_FOR_JSON_REFERENCE_TUPLES + CHECK(json(std::forward_as_tuple(j)) == json::array({true})); +#endif + } +#endif + SECTION("PR #2181 - regression bug with lvalue") { // see https://github.com/nlohmann/json/pull/2181#issuecomment-653326060