From 6b97599a274b9b72caffa1332d5384c9aac27590 Mon Sep 17 00:00:00 2001 From: Florian Albrechtskirchinger Date: Sun, 29 May 2022 13:08:06 +0200 Subject: [PATCH] Fix C++20/gcc-12 issues (Part 2) (#3446) * Add C++20 3-way comparison operator and fix broken comparisons Fixes #3207. Fixes #3409. * Fix iterators to meet (more) std::ranges requirements Fixes #3130. Related discussion: #3408 * Add note about CMake standard version selection to unit tests Document how CMake chooses which C++ standard version to use when building tests. * Update documentation * CI: add legacy discarded value comparison * Fix internal linkage errors when building a module --- .github/workflows/ubuntu.yml | 2 +- CMakeLists.txt | 24 +- cmake/ci.cmake | 19 +- cmake/test.cmake | 17 +- docs/mkdocs/docs/api/basic_json/index.md | 3 +- .../mkdocs/docs/api/basic_json/operator_eq.md | 37 +- .../mkdocs/docs/api/basic_json/operator_ge.md | 40 +- .../mkdocs/docs/api/basic_json/operator_gt.md | 38 +- .../mkdocs/docs/api/basic_json/operator_le.md | 40 +- .../mkdocs/docs/api/basic_json/operator_lt.md | 61 +- .../mkdocs/docs/api/basic_json/operator_ne.md | 39 +- .../docs/api/basic_json/operator_spaceship.md | 70 ++ docs/mkdocs/docs/api/basic_json/value_t.md | 39 +- docs/mkdocs/docs/api/macros/index.md | 8 + .../mkdocs/docs/api/macros/json_has_ranges.md | 18 + .../macros/json_has_three_way_comparison.md | 19 + ...n_use_legacy_discarded_value_comparison.md | 61 ++ docs/mkdocs/docs/css/custom.css | 4 + docs/mkdocs/mkdocs.yml | 15 +- .../nlohmann/detail/conversions/from_json.hpp | 49 +- .../nlohmann/detail/conversions/to_json.hpp | 47 +- .../nlohmann/detail/iterators/iter_impl.hpp | 5 +- .../detail/iterators/iteration_proxy.hpp | 65 +- include/nlohmann/detail/macro_scope.hpp | 31 +- include/nlohmann/detail/macro_unscope.hpp | 3 + include/nlohmann/detail/value_t.hpp | 30 +- include/nlohmann/json.hpp | 374 +++++---- single_include/nlohmann/json.hpp | 759 +++++++++++------- tests/CMakeLists.txt | 9 + tests/src/unit-class_parser.cpp | 8 +- tests/src/unit-comparison.cpp | 554 ++++++++++--- tests/src/unit-conversions.cpp | 15 +- tests/src/unit-items.cpp | 32 +- tests/src/unit-iterators2.cpp | 109 +++ tests/src/unit-regression2.cpp | 9 +- 35 files changed, 1963 insertions(+), 690 deletions(-) create mode 100644 docs/mkdocs/docs/api/basic_json/operator_spaceship.md create mode 100644 docs/mkdocs/docs/api/macros/json_has_ranges.md create mode 100644 docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md create mode 100644 docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md create mode 100644 docs/mkdocs/docs/css/custom.css diff --git a/.github/workflows/ubuntu.yml b/.github/workflows/ubuntu.yml index 4d037e060..39098e4d7 100644 --- a/.github/workflows/ubuntu.yml +++ b/.github/workflows/ubuntu.yml @@ -48,7 +48,7 @@ jobs: container: ghcr.io/nlohmann/json-ci:v2.3.0 strategy: matrix: - target: [ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions] + target: [ci_test_diagnostics, ci_test_noexceptions, ci_test_noimplicitconversions, ci_test_legacycomparison] steps: - uses: actions/checkout@v3 - name: cmake diff --git a/CMakeLists.txt b/CMakeLists.txt index 8f65d9a32..632e6dec2 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -37,13 +37,14 @@ if(${MAIN_PROJECT} AND (${CMAKE_VERSION} VERSION_EQUAL 3.13 OR ${CMAKE_VERSION} else() set(JSON_BuildTests_INIT OFF) endif() -option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${JSON_BuildTests_INIT}) -option(JSON_CI "Enable CI build targets." OFF) -option(JSON_Diagnostics "Use extended diagnostic messages." OFF) -option(JSON_ImplicitConversions "Enable implicit conversions." ON) -option(JSON_Install "Install CMake targets during install step." ${MAIN_PROJECT}) -option(JSON_MultipleHeaders "Use non-amalgamated version of the library." OFF) -option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) +option(JSON_BuildTests "Build the unit tests when BUILD_TESTING is enabled." ${JSON_BuildTests_INIT}) +option(JSON_CI "Enable CI build targets." OFF) +option(JSON_Diagnostics "Use extended diagnostic messages." OFF) +option(JSON_ImplicitConversions "Enable implicit conversions." ON) +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." OFF) +option(JSON_SystemInclude "Include as system headers (skip for clang-tidy)." OFF) if (JSON_CI) include(ci) @@ -77,6 +78,10 @@ if (NOT JSON_ImplicitConversions) message(STATUS "Implicit conversions are disabled") endif() +if (JSON_LegacyDiscardedValueComparison) + message(STATUS "Legacy discarded value comparison enabled") +endif() + if (JSON_Diagnostics) message(STATUS "Diagnostics enabled") endif() @@ -100,8 +105,9 @@ endif() target_compile_definitions( ${NLOHMANN_JSON_TARGET_NAME} INTERFACE - JSON_USE_IMPLICIT_CONVERSIONS=$ - JSON_DIAGNOSTICS=$ + $<$>:JSON_USE_IMPLICIT_CONVERSIONS=0> + $<$:JSON_DIAGNOSTICS=1> + $<$:JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1> ) target_include_directories( diff --git a/cmake/ci.cmake b/cmake/ci.cmake index 56a80c066..ed26e02b5 100644 --- a/cmake/ci.cmake +++ b/cmake/ci.cmake @@ -498,6 +498,20 @@ add_custom_target(ci_test_diagnostics COMMENT "Compile and test with improved diagnostics enabled" ) +############################################################################### +# Enable legacy discarded value comparison. +############################################################################### + +add_custom_target(ci_test_legacycomparison + COMMAND CXX=${CLANG_TOOL} ${CMAKE_COMMAND} + -DCMAKE_BUILD_TYPE=Debug -GNinja + -DJSON_BuildTests=ON -DJSON_MultipleHeaders=ON -DJSON_LegacyDiscardedValueComparison=ON + -S${PROJECT_SOURCE_DIR} -B${PROJECT_BINARY_DIR}/build_legacycomparison + COMMAND ${CMAKE_COMMAND} --build ${PROJECT_BINARY_DIR}/build_legacycomparison + COMMAND cd ${PROJECT_BINARY_DIR}/build_legacycomparison && ${CMAKE_CTEST_COMMAND} --parallel ${N} --output-on-failure + COMMENT "Compile and test with legacy discarded value comparison enabled" +) + ############################################################################### # Coverage. ############################################################################### @@ -797,8 +811,9 @@ endfunction() ci_get_cmake(3.1.0 CMAKE_3_1_0_BINARY) ci_get_cmake(3.13.0 CMAKE_3_13_0_BINARY) -set(JSON_CMAKE_FLAGS_3_1_0 "JSON_Install;JSON_MultipleHeaders;JSON_ImplicitConversions;JSON_Valgrind;JSON_Diagnostics;JSON_SystemInclude") -set(JSON_CMAKE_FLAGS_3_13_0 "JSON_BuildTests") +set(JSON_CMAKE_FLAGS_3_1_0 JSON_Diagnostics JSON_ImplicitConversions JSON_LegacyDiscardedValueComparison + JSON_Install JSON_MultipleHeaders JSON_SystemInclude JSON_Valgrind) +set(JSON_CMAKE_FLAGS_3_13_0 JSON_BuildTests) function(ci_add_cmake_flags_targets flag min_version) string(TOLOWER "ci_cmake_flag_${flag}" flag_target) diff --git a/cmake/test.cmake b/cmake/test.cmake index 1d146646b..89b7c6ec7 100644 --- a/cmake/test.cmake +++ b/cmake/test.cmake @@ -156,6 +156,7 @@ endfunction() ############################################################################# # json_test_add_test_for( # +# [NAME ] # MAIN
# [CXX_STANDARDS ...] [FORCE]) # @@ -165,6 +166,7 @@ endfunction() # # if C++ standard is supported by the compiler and the # source file contains JSON_HAS_CPP_. +# Use NAME to override the filename-derived test name. # Use FORCE to create the test regardless of the file containing # JSON_HAS_CPP_. # Test targets are linked against
. @@ -172,15 +174,22 @@ endfunction() ############################################################################# function(json_test_add_test_for file) - cmake_parse_arguments(args "FORCE" "MAIN" "CXX_STANDARDS" ${ARGN}) - - get_filename_component(file_basename ${file} NAME_WE) - string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + cmake_parse_arguments(args "FORCE" "MAIN;NAME" "CXX_STANDARDS" ${ARGN}) if("${args_MAIN}" STREQUAL "") message(FATAL_ERROR "Required argument MAIN
missing.") endif() + if("${args_NAME}" STREQUAL "") + get_filename_component(file_basename ${file} NAME_WE) + string(REGEX REPLACE "unit-([^$]+)" "test-\\1" test_name ${file_basename}) + else() + set(test_name ${args_NAME}) + if(NOT test_name MATCHES "test-[^$]+") + message(FATAL_ERROR "Test name must start with 'test-'.") + endif() + endif() + if("${args_CXX_STANDARDS}" STREQUAL "") set(args_CXX_STANDARDS 11) endif() diff --git a/docs/mkdocs/docs/api/basic_json/index.md b/docs/mkdocs/docs/api/basic_json/index.md index bc4dba153..2191d671f 100644 --- a/docs/mkdocs/docs/api/basic_json/index.md +++ b/docs/mkdocs/docs/api/basic_json/index.md @@ -233,9 +233,10 @@ Access to the JSON value - [**operator==**](operator_eq.md) - comparison: equal - [**operator!=**](operator_ne.md) - comparison: not equal - [**operator<**](operator_lt.md) - comparison: less than -- [**operator<=**](operator_le.md) - comparison: less than or equal - [**operator>**](operator_gt.md) - comparison: greater than +- [**operator<=**](operator_le.md) - comparison: less than or equal - [**operator>=**](operator_ge.md) - comparison: greater than or equal +- [**operator<=>**](operator_spaceship.md) - comparison: 3-way ### Serialization / Dumping diff --git a/docs/mkdocs/docs/api/basic_json/operator_eq.md b/docs/mkdocs/docs/api/basic_json/operator_eq.md index 3eec4fda4..6c86bf13e 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_eq.md +++ b/docs/mkdocs/docs/api/basic_json/operator_eq.md @@ -1,21 +1,31 @@ # nlohmann::basic_json::operator== ```cpp -bool operator==(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator==(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator==(const_reference lhs, const ScalarType rhs) noexcept; +bool operator==(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator==(ScalarType lhs, const const_reference rhs) noexcept; +bool operator==(ScalarType lhs, const const_reference rhs) noexcept; // (2) + +// since C++20 +class basic_json { + bool operator==(const_reference rhs) const noexcept; // (1) + + template + bool operator==(ScalarType rhs) const noexcept; // (2) +}; ``` -Compares two JSON values for equality according to the following rules: +1. Compares two JSON values for equality according to the following rules: + - Two JSON values are equal if (1) neither value is discarded, or (2) they are of the same + type and their stored values are the same according to their respective `operator==`. + - Integer and floating-point numbers are automatically converted before comparison. -- Two JSON values are equal if (1) they are not discarded, (2) they are from the same type, and (3) their stored values - are the same according to their respective `operator==`. -- Integer and floating-point numbers are automatically converted before comparison. Note that two NaN values are always - treated as unequal. +2. Compares a JSON value and a scalar or a scalar and a JSON value for equality by converting the + scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -32,7 +42,7 @@ Compares two JSON values for equality according to the following rules: ## Return value -whether the values `lhs` and `rhs` are equal +whether the values `lhs`/`*this` and `rhs` are equal ## Exception safety @@ -46,7 +56,11 @@ Linear. !!! note "Comparing special values" - - NaN values never compare equal to themselves or to other NaN values. + - `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. - JSON `#!cpp null` values are all equal. - Discarded values never compare equal to themselves. @@ -117,4 +131,5 @@ Linear. ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. +2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ge.md b/docs/mkdocs/docs/api/basic_json/operator_ge.md index 68aac6557..6730f6809 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ge.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ge.md @@ -1,17 +1,25 @@ # nlohmann::basic_json::operator>= ```cpp -bool operator>=(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator>=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator>=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator>=(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` by calculating -`#!cpp !(lhs < rhs)`. +1. Compares whether one JSON value `lhs` is greater than or equal to another JSON value `rhs` + according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs < rhs)`. + +2. Compares wether a JSON value is greater than or equal to a scalar or a scalar is greater than or + equal to a JSON value by converting the scalar to a JSON value and comparing both JSON values + according to 1. ## Template parameters @@ -38,6 +46,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -54,6 +77,11 @@ Linear. --8<-- "examples/operator__greaterequal.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_gt.md b/docs/mkdocs/docs/api/basic_json/operator_gt.md index 92ec30594..540e6076d 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_gt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_gt.md @@ -1,16 +1,24 @@ # nlohmann::basic_json::operator> ```cpp -bool operator>(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator>(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator>(const_reference lhs, const ScalarType rhs) noexcept; +bool operator>(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator>(ScalarType lhs, const const_reference rhs) noexcept; +bool operator>(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is greater than another JSON value `rhs` by calculating `#!cpp !(lhs <= rhs)`. +1. Compares whether one JSON value `lhs` is greater than another JSON value `rhs` according to the + following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs <= rhs)`. + +2. Compares wether a JSON value is greater than a scalar or a scalar is greater than a JSON value by + converting the scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -37,6 +45,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -53,6 +76,11 @@ Linear. --8<-- "examples/operator__greater.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_le.md b/docs/mkdocs/docs/api/basic_json/operator_le.md index 54f9a2809..c0f90acb2 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_le.md +++ b/docs/mkdocs/docs/api/basic_json/operator_le.md @@ -1,17 +1,25 @@ # nlohmann::basic_json::operator<= ```cpp -bool operator<=(const_reference lhs, const_reference rhs) noexcept, +// until C++20 +bool operator<=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator<=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator<=(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` by calculating -`#cpp !(rhs < lhs)`. +1. Compares whether one JSON value `lhs` is less than or equal to another JSON value `rhs` + according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(rhs < lhs)`. + +1. Compares wether a JSON value is less than or equal to a scalar or a scalar is less than or equal + to a JSON value by converting the scalar to a JSON value and comparing both JSON values according + to 1. ## Template parameters @@ -38,6 +46,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -54,6 +77,11 @@ Linear. --8<-- "examples/operator__lessequal.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_lt.md b/docs/mkdocs/docs/api/basic_json/operator_lt.md index d1a4999b4..b5d191ec4 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_lt.md +++ b/docs/mkdocs/docs/api/basic_json/operator_lt.md @@ -1,31 +1,34 @@ # nlohmann::basic_json::operator< ```cpp -bool operator<(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator<(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator<(const_reference lhs, const ScalarType rhs) noexcept; +bool operator<(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator<(ScalarType lhs, const const_reference rhs) noexcept; +bool operator<(ScalarType lhs, const const_reference rhs) noexcept; // (2) ``` -Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the following rules: +1. Compares whether one JSON value `lhs` is less than another JSON value `rhs` according to the + following rules: + - If either operand is discarded, the comparison yields `#!cpp false`. + - If both operands have the same type, the values are compared using their respective `operator<`. + - Integer and floating-point numbers are automatically converted before comparison. + - In case `lhs` and `rhs` have different types, the values are ignored and the order of the types + is considered, which is: + 1. null + 2. boolean + 3. number (all types) + 4. object + 5. array + 6. string + 7. binary + For instance, any boolean value is considered less than any string. -- If `lhs` and `rhs` have the same type, the values are compared using the default `<` operator. -- Integer and floating-point numbers are automatically converted before comparison -- Discarded values a -- In case `lhs` and `rhs` have different types, the values are ignored and the order of the types is considered, which - is: - 1. null - 2. boolean - 3. number (all types) - 4. object - 5. array - 6. string - 7. binary - - For instance, any boolean value is considered less than any string. +2. Compares wether a JSON value is less than a scalar or a scalar is less than a JSON value by converting + the scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -52,6 +55,21 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +!!! note "Operator overload resolution" + + Since C++20 overload resolution will consider the _rewritten candidate_ generated from + [`operator<=>`](operator_spaceship.md). + ## Examples ??? example @@ -68,6 +86,11 @@ Linear. --8<-- "examples/operator__less.output" ``` +## See also + +- [**operator<=>**](operator_spaceship.md) comparison: 3-way + ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. +2. Added in version 1.0.0. Conditionally removed since C++20 in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_ne.md b/docs/mkdocs/docs/api/basic_json/operator_ne.md index e94da9b76..f5d989b50 100644 --- a/docs/mkdocs/docs/api/basic_json/operator_ne.md +++ b/docs/mkdocs/docs/api/basic_json/operator_ne.md @@ -1,16 +1,32 @@ # nlohmann::basic_json::operator!= ```cpp -bool operator!=(const_reference lhs, const_reference rhs) noexcept; +// until C++20 +bool operator!=(const_reference lhs, const_reference rhs) noexcept; // (1) template -bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; +bool operator!=(const_reference lhs, const ScalarType rhs) noexcept; // (2) template -bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; +bool operator!=(ScalarType lhs, const const_reference rhs) noexcept; // (2) + +// since C++20 +class basic_json { + bool operator!=(const_reference rhs) const noexcept; // (1) + + template + bool operator!=(ScalarType rhs) const noexcept; // (2) +}; ``` -Compares two JSON values for inequality by calculating `#!cpp !(lhs == rhs)`. +1. Compares two JSON values for inequality according to the following rules: + - The comparison always yields `#!cpp false` if (1) either operand is discarded, or (2) either + operand is `NaN` and the other operand is either `NaN` or any other number. + - Otherwise, returns the result of `#!cpp !(lhs == rhs)` (until C++20) or + `#!cpp !(*this == rhs)` (since C++20). + +2. Compares a JSON value and a scalar or a scalar and a JSON value for inequality by converting the + scalar to a JSON value and comparing both JSON values according to 1. ## Template parameters @@ -27,7 +43,7 @@ Compares two JSON values for inequality by calculating `#!cpp !(lhs == rhs)`. ## Return value -whether the values `lhs` and `rhs` are not equal +whether the values `lhs`/`*this` and `rhs` are not equal ## Exception safety @@ -37,6 +53,16 @@ No-throw guarantee: this function never throws exceptions. Linear. +## Notes + +!!! note "Comparing `NaN`" + + `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `#!cpp false`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + ## Examples ??? example @@ -69,4 +95,5 @@ Linear. ## Version history -- Added in version 1.0.0. +1. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. +2. Added in version 1.0.0. Added C++20 member functions in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/operator_spaceship.md b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md new file mode 100644 index 000000000..4beba4f86 --- /dev/null +++ b/docs/mkdocs/docs/api/basic_json/operator_spaceship.md @@ -0,0 +1,70 @@ +# nlohmann::basic_json::operator<=> + +```cpp +// since C++20 +class basic_json { + std::partial_ordering operator<=>(const_reference rhs) const noexcept; // (1) + + template + std::partial_ordering operator<=>(const ScalarType rhs) const noexcept; // (2) +}; +``` + +1. 3-way compares two JSON values producing a result of type `std::partial_ordering` according to the following rules: + - Two JSON values compare with a result of `std::partial_ordering::unordered` if either value is discarded. + - If both JSON values are of the same type, the result is produced by 3-way comparing their stored values using their + respective `operator<=>`. + - Integer and floating-point numbers are converted to their common type and then 3-way compared using their respective + `operator<=>`. + For instance, comparing an integer and a floating-point value will 3-way compare the first value convertered to + floating-point with the second value. + - Otherwise, yields a result by comparing the type (see [`value_t`](value_t.md)). + +2. 3-way compares a JSON value and a scalar or a scalar and a JSON value by converting the scalar to a JSON value and 3-way + comparing both JSON values (see 1). + +## Template parameters + +`ScalarType` +: a scalar type according to `std::is_scalar::value` + +## Parameters + +`rhs` (in) +: second value to consider + +## Return value + +the `std::partial_ordering` of the 3-way comparison of `*this` and `rhs` + +## Exception safety + +No-throw guarantee: this function never throws exceptions. + +## Complexity + +Linear. + +## Notes + +!!! note "Comparing `NaN`" + + - `NaN` values are unordered within the domain of numbers. + The following comparisons all yield `std::partial_ordering::unordered`: + 1. Comparing a `NaN` with itself. + 2. Comparing a `NaN` with another `NaN`. + 3. Comparing a `NaN` and any other number. + +## See also + +- [**operator==**](operator_eq.md) - comparison: equal +- [**operator!=**](operator_ne.md) - comparison: not equal +- [**operator<**](operator_lt.md) - comparison: less than +- [**operator<=**](operator_le.md) - comparison: less than or equal +- [**operator>**](operator_gt.md) - comparison: greater than +- [**operator>=**](operator_ge.md) - comparison: greater than or equal + +## Version history + +1. Added in version 3.11.0. +2. Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/basic_json/value_t.md b/docs/mkdocs/docs/api/basic_json/value_t.md index e7d32c480..f83574083 100644 --- a/docs/mkdocs/docs/api/basic_json/value_t.md +++ b/docs/mkdocs/docs/api/basic_json/value_t.md @@ -24,10 +24,41 @@ functions [`is_null`](is_null.md), [`is_object`](is_object.md), [`is_array`](is_ ## Notes -There are three enumeration entries (number_integer, number_unsigned, and number_float), because the library -distinguishes these three types for numbers: [`number_unsigned_t`](number_unsigned_t.md) is used for unsigned integers, -[`number_integer_t`](number_integer_t.md) is used for signed integers, and [`number_float_t`](number_float_t.md) is used -for floating-point numbers or to approximate integers which do not fit in the limits of their respective type. +!!! note "Ordering" + + The order of types is as follows: + + 1. `null` + 2. `boolean` + 3. `number_integer`, `number_unsigned`, `number_float` + 4. `object` + 5. `array` + 6. `string` + 7. `binary` + + `discarded` is unordered. + +!!! note "Types of numbers" + + There are three enumerators for numbers (`number_integer`, `number_unsigned`, and `number_float`) to distinguish + between different types of numbers: + + - [`number_unsigned_t`](number_unsigned_t.md) for unsigned integers + - [`number_integer_t`](number_integer_t.md) for signed integers + - [`number_float_t`](number_float_t.md) for floating-point numbers or to approximate integers which do not fit + into the limits of their respective type + +!!! warning "Comparison operators" + + `operator<` and `operator<=>` (since C++20) are overloaded and compare according to the ordering described above. + Until C++20 all other relational and equality operators yield results according to the integer value of each + enumerator. + Since C++20 some compilers consider the _rewritten candidates_ generated from `operator<=>` during overload + resolution, while others do not. + For predictable and portable behavior use: + + - `operator<` or `operator<=>` when wanting to compare according to the order described above + - `operator==` or `operator!=` when wanting to compare according to each enumerators integer value ## Examples diff --git a/docs/mkdocs/docs/api/macros/index.md b/docs/mkdocs/docs/api/macros/index.md index 56924da44..5f0a7a194 100644 --- a/docs/mkdocs/docs/api/macros/index.md +++ b/docs/mkdocs/docs/api/macros/index.md @@ -17,6 +17,8 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_HAS_CPP_11**
**JSON_HAS_CPP_14**
**JSON_HAS_CPP_17**
**JSON_HAS_CPP_20**](json_has_cpp_11.md) - set supported C++ standard - [**JSON_HAS_FILESYSTEM**
**JSON_HAS_EXPERIMENTAL_FILESYSTEM**](json_has_filesystem.md) - control `std::filesystem` support +- [**JSON_HAS_RANGES**](json_has_ranges.md) - control `std::ranges` support +- [**JSON_HAS_THREE_WAY_COMPARISON**](json_has_three_way_comparison.md) - control 3-way comparison support - [**JSON_NO_IO**](json_no_io.md) - switch off functions relying on certain C++ I/O headers - [**JSON_SKIP_UNSUPPORTED_COMPILER_CHECK**](json_skip_unsupported_compiler_check.md) - do not warn about unsupported compilers @@ -29,6 +31,12 @@ header. See also the [macro overview page](../../features/macros.md). - [**JSON_USE_IMPLICIT_CONVERSIONS**](json_use_implicit_conversions.md) - control implicit conversions + +## Comparison behavior + +- [**JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON**](json_use_legacy_discarded_value_comparison.md) - + control comparison of discarded values + ## Serialization/deserialization macros - [**NLOHMANN_DEFINE_TYPE_INTRUSIVE(type, member...)**
**NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT(type, member...)**](nlohmann_define_type_intrusive.md) - serialization/deserialization of types _with_ access to private variables diff --git a/docs/mkdocs/docs/api/macros/json_has_ranges.md b/docs/mkdocs/docs/api/macros/json_has_ranges.md new file mode 100644 index 000000000..ae596979e --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_has_ranges.md @@ -0,0 +1,18 @@ +# JSON_HAS_RANGES + +```cpp +#define JSON_HAS_RANGES /* value */ +``` + +This macro indicates whether the standard library has any support for ranges. Implies support for concepts. +Possible values are `1` when supported or `0` when unsupported. + +## Default definition + +The default value is detected based on the preprocessor macro `#!cpp __cpp_lib_ranges`. + +When the macro is not defined, the library will define it to its default value. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md new file mode 100644 index 000000000..fc1dcb43c --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_has_three_way_comparison.md @@ -0,0 +1,19 @@ +# JSON_HAS_THREE_WAY_COMPARISON + +```cpp +#define JSON_HAS_THREE_WAY_COMPARISON /* value */ +``` + +This macro indicates whether the compiler and standard library support 3-way comparison. +Possible values are `1` when supported or `0` when unsupported. + +## Default definition + +The default value is detected based on the preprocessor macros `#!cpp __cpp_impl_three_way_comparison` +and `#!cpp __cpp_lib_three_way_comparison`. + +When the macro is not defined, the library will define it to its default value. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md new file mode 100644 index 000000000..4f630db12 --- /dev/null +++ b/docs/mkdocs/docs/api/macros/json_use_legacy_discarded_value_comparison.md @@ -0,0 +1,61 @@ +# JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + +```cpp +#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON /* value */ +``` + +This macro enables the (incorrect) legacy comparison behavior of discarded JSON values. +Possible values are `1` to enable or `0` to disable (default). + +When enabled, comparisons involving at least one discarded JSON value yield results as follows: + +| **Operator** | **Result** | +|--------------|---------------| +| `==` | `#!cpp false` | +| `!=` | `#!cpp true` | +| `<` | `#!cpp false` | +| `<=` | `#!cpp true` | +| `>=` | `#!cpp true` | +| `>` | `#!cpp false` | + +Otherwise, comparisons involving at least one discarded JSON value always yield `#!cpp false`. + +## Default definition + +The default value is `0`. + +```cpp +#define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +``` + +When the macro is not defined, the library will define it to its default value. + +## Notes + +!!! warning "Inconsistent behavior in C++20 and beyond" + + When targeting C++20 or above, enabling the legacy comparison behavior is _strongly_ + discouraged. + + - The 3-way comparison operator (`<=>`) will always give the correct result + (`#!cpp std::partial_ordering::unordered`) regardless of the value of + `JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON`. + - Overloads for the equality and relational operators emulate the legacy behavior. + + Code outside your control may use either 3-way comparison or the equality and + relational operators, resulting in inconsistent and unpredictable behavior. + + See [`operator<=>`](../basic_json/operator_spaceship.md) for more information on 3-way + comparison. + +!!! warning "Deprecation" + + The legacy comparison behavior is deprecated and may be removed in a future major + version release. + + New code should not depend on it and existing code should try to remove or rewrite + expressions relying on it. + +## Version history + +- Added in version 3.11.0. diff --git a/docs/mkdocs/docs/css/custom.css b/docs/mkdocs/docs/css/custom.css new file mode 100644 index 000000000..7a1008b0b --- /dev/null +++ b/docs/mkdocs/docs/css/custom.css @@ -0,0 +1,4 @@ +/* disable ligatures in code and preformatted blocks */ +code, pre { + font-variant-ligatures: none; +} diff --git a/docs/mkdocs/mkdocs.yml b/docs/mkdocs/mkdocs.yml index dd4da8a0c..08ef336db 100644 --- a/docs/mkdocs/mkdocs.yml +++ b/docs/mkdocs/mkdocs.yml @@ -154,15 +154,16 @@ nav: - 'operator value_t': api/basic_json/operator_value_t.md - 'operator[]': api/basic_json/operator[].md - 'operator=': api/basic_json/operator=.md + - 'operator+=': api/basic_json/operator+=.md - 'operator==': api/basic_json/operator_eq.md - 'operator!=': api/basic_json/operator_ne.md - 'operator<': api/basic_json/operator_lt.md - - 'operator<<': api/basic_json/operator_ltlt.md - - 'operator<=': api/basic_json/operator_le.md - 'operator>': api/basic_json/operator_gt.md - - 'operator>>': api/basic_json/operator_gtgt.md + - 'operator<=': api/basic_json/operator_le.md - 'operator>=': api/basic_json/operator_ge.md - - 'operator+=': api/basic_json/operator+=.md + - 'operator<=>': api/basic_json/operator_spaceship.md + - 'operator<<': api/basic_json/operator_ltlt.md + - 'operator>>': api/basic_json/operator_gtgt.md - 'operator""_json': api/basic_json/operator_literal_json.md - 'operator""_json_pointer': api/basic_json/operator_literal_json_pointer.md - 'out_of_range': api/basic_json/out_of_range.md @@ -246,6 +247,8 @@ nav: - 'JSON_HAS_CPP_20': api/macros/json_has_cpp_11.md - 'JSON_HAS_EXPERIMENTAL_FILESYSTEM': api/macros/json_has_filesystem.md - 'JSON_HAS_FILESYSTEM': api/macros/json_has_filesystem.md + - 'JSON_HAS_RANGES': api/macros/json_has_ranges.md + - 'JSON_HAS_THREE_WAY_COMPARISON': api/macros/json_has_three_way_comparison.md - 'JSON_NOEXCEPTION': api/macros/json_noexception.md - 'JSON_NO_IO': api/macros/json_no_io.md - 'JSON_SKIP_LIBRARY_VERSION_CHECK': api/macros/json_skip_library_version_check.md @@ -253,6 +256,7 @@ nav: - 'JSON_THROW_USER': api/macros/json_throw_user.md - 'JSON_TRY_USER': api/macros/json_throw_user.md - 'JSON_USE_IMPLICIT_CONVERSIONS': api/macros/json_use_implicit_conversions.md + - 'JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON': api/macros/json_use_legacy_discarded_value_comparison.md - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE': api/macros/nlohmann_define_type_intrusive.md - 'NLOHMANN_DEFINE_TYPE_INTRUSIVE_WITH_DEFAULT': api/macros/nlohmann_define_type_intrusive.md - 'NLOHMANN_DEFINE_TYPE_NON_INTRUSIVE': api/macros/nlohmann_define_type_non_intrusive.md @@ -319,5 +323,8 @@ plugins: minify_html: true - git-revision-date-localized +extra_css: + - css/custom.css + extra_javascript: - https://cdnjs.cloudflare.com/ajax/libs/mathjax/2.7.0/MathJax.js?config=TeX-MML-AM_CHTML diff --git a/include/nlohmann/detail/conversions/from_json.hpp b/include/nlohmann/detail/conversions/from_json.hpp index 079baa723..f0a93f291 100644 --- a/include/nlohmann/detail/conversions/from_json.hpp +++ b/include/nlohmann/detail/conversions/from_json.hpp @@ -39,7 +39,7 @@ namespace nlohmann namespace detail { template -void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { @@ -86,7 +86,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { @@ -96,7 +96,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -111,7 +111,7 @@ template < std::is_assignable::value && !std::is_same::value && !is_json_ref::value, int > = 0 > -void from_json(const BasicJsonType& j, StringType& s) +inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -122,26 +122,26 @@ void from_json(const BasicJsonType& j, StringType& s) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) +inline void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); @@ -151,7 +151,7 @@ void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) +inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -168,7 +168,7 @@ void from_json(const BasicJsonType& j, std::forward_list& l) // valarray doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) +inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -193,7 +193,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } template -void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } @@ -237,8 +237,8 @@ template::value, int> = 0> -void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) { using std::end; @@ -295,7 +295,7 @@ auto from_json(BasicJsonType&& j, identity_tag> tag) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { @@ -307,7 +307,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) template::value, int> = 0> -void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { @@ -339,7 +339,7 @@ template < typename BasicJsonType, typename ArithmeticType, !std::is_same::value&& !std::is_same::value, int > = 0 > -void from_json(const BasicJsonType& j, ArithmeticType& val) +inline void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { @@ -389,7 +389,7 @@ std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) { p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); } @@ -401,7 +401,7 @@ std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) { t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } @@ -421,7 +421,7 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t) template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::map& m) +inline void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -441,7 +441,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) +inline void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -460,7 +460,7 @@ void from_json(const BasicJsonType& j, std::unordered_map -void from_json(const BasicJsonType& j, std_fs::path& p) +inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -482,11 +482,16 @@ struct from_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann diff --git a/include/nlohmann/detail/conversions/to_json.hpp b/include/nlohmann/detail/conversions/to_json.hpp index 7628ae011..fde46ef7c 100644 --- a/include/nlohmann/detail/conversions/to_json.hpp +++ b/include/nlohmann/detail/conversions/to_json.hpp @@ -267,55 +267,55 @@ struct external_constructor template::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept +inline void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) +inline void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept +inline void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept +inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template -void to_json(BasicJsonType& j, const std::vector& e) +inline void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } @@ -328,39 +328,39 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template -void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> -void to_json(BasicJsonType& j, const std::valarray& arr) +inline void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +inline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } @@ -370,13 +370,13 @@ template < enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) int > = 0 > -void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +inline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > -void to_json(BasicJsonType& j, const std::pair& p) +inline void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } @@ -384,26 +384,26 @@ void to_json(BasicJsonType& j, const std::pair& p) // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> -void to_json(BasicJsonType& j, const T& b) +inline void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> -void to_json(BasicJsonType& j, const T& t) +inline void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template -void to_json(BasicJsonType& j, const std_fs::path& p) +inline void to_json(BasicJsonType& j, const std_fs::path& p) { j = p.string(); } @@ -420,11 +420,16 @@ struct to_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index cdcdaff1f..81c61b3e0 100644 --- a/include/nlohmann/detail/iterators/iter_impl.hpp +++ b/include/nlohmann/detail/iterators/iter_impl.hpp @@ -51,9 +51,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of::value + && std::is_base_of::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 diff --git a/include/nlohmann/detail/iterators/iteration_proxy.hpp b/include/nlohmann/detail/iterators/iteration_proxy.hpp index 9994b364c..a75614de1 100644 --- a/include/nlohmann/detail/iterators/iteration_proxy.hpp +++ b/include/nlohmann/detail/iterators/iteration_proxy.hpp @@ -6,6 +6,10 @@ #include // tuple_size, get, tuple_element #include // move +#if JSON_HAS_RANGES + #include // enable_borrowed_range +#endif + #include #include @@ -25,14 +29,14 @@ template class iteration_proxy_value public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; - using pointer = value_type * ; - using reference = value_type & ; + using pointer = value_type *; + using reference = value_type &; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator - IteratorType anchor; + IteratorType anchor{}; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index @@ -40,15 +44,30 @@ template class iteration_proxy_value /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) - const string_type empty_str{}; + string_type empty_str{}; public: - explicit iteration_proxy_value(IteratorType it) noexcept + explicit iteration_proxy_value() = default; + explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_default_constructible::value) : anchor(std::move(it)) + , array_index(array_index_) {} + iteration_proxy_value(iteration_proxy_value const&) = default; + iteration_proxy_value& operator=(iteration_proxy_value const&) = default; + // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions + iteration_proxy_value(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) = default; + iteration_proxy_value& operator=(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_assignable::value + && std::is_nothrow_move_assignable::value) = default; + ~iteration_proxy_value() = default; + /// dereference operator (needed for range-based for) - iteration_proxy_value& operator*() + const iteration_proxy_value& operator*() const { return *this; } @@ -62,6 +81,14 @@ template class iteration_proxy_value return *this; } + iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto tmp = iteration_proxy_value(anchor, array_index); + ++anchor; + ++array_index; + return tmp; + } + /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { @@ -122,25 +149,34 @@ template class iteration_proxy { private: /// the container to iterate - typename IteratorType::reference container; + typename IteratorType::pointer container = nullptr; public: + explicit iteration_proxy() = default; + /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} + : container(&cont) {} + + iteration_proxy(iteration_proxy const&) = default; + iteration_proxy& operator=(iteration_proxy const&) = default; + iteration_proxy(iteration_proxy&&) noexcept = default; + iteration_proxy& operator=(iteration_proxy&&) noexcept = default; + ~iteration_proxy() = default; /// return iterator begin (needed for range-based for) - iteration_proxy_value begin() noexcept + iteration_proxy_value begin() const noexcept { - return iteration_proxy_value(container.begin()); + return iteration_proxy_value(container->begin()); } /// return iterator end (needed for range-based for) - iteration_proxy_value end() noexcept + iteration_proxy_value end() const noexcept { - return iteration_proxy_value(container.end()); + return iteration_proxy_value(container->end()); } }; + // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 @@ -187,3 +223,8 @@ class tuple_element> #pragma clang diagnostic pop #endif } // namespace std + +#if JSON_HAS_RANGES + template + inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; +#endif diff --git a/include/nlohmann/detail/macro_scope.hpp b/include/nlohmann/detail/macro_scope.hpp index cc9ac5fc7..18a527c19 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -37,6 +37,12 @@ #define JSON_HAS_CPP_11 #endif +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + #if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) #ifdef JSON_HAS_CPP_17 #if defined(__cpp_lib_filesystem) @@ -98,14 +104,31 @@ #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L #define JSON_HAS_THREE_WAY_COMPARISON 1 #else #define JSON_HAS_THREE_WAY_COMPARISON 0 #endif #endif +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else @@ -429,3 +452,7 @@ #ifndef JSON_DIAGNOSTICS #define JSON_DIAGNOSTICS 0 #endif + +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif diff --git a/include/nlohmann/detail/macro_unscope.hpp b/include/nlohmann/detail/macro_unscope.hpp index ec57b02cc..396fe1a33 100644 --- a/include/nlohmann/detail/macro_unscope.hpp +++ b/include/nlohmann/detail/macro_unscope.hpp @@ -14,6 +14,7 @@ #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #ifndef JSON_TEST_KEEP_MACROS @@ -26,6 +27,8 @@ #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif #include diff --git a/include/nlohmann/detail/value_t.hpp b/include/nlohmann/detail/value_t.hpp index a98c4355a..5a32b646e 100644 --- a/include/nlohmann/detail/value_t.hpp +++ b/include/nlohmann/detail/value_t.hpp @@ -5,6 +5,11 @@ #include // uint8_t #include // string +#include +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + namespace nlohmann { namespace detail @@ -64,7 +69,11 @@ Returns an ordering that is similar to Python: @since version 1.0.0 */ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif { static constexpr std::array order = {{ 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, @@ -75,7 +84,26 @@ inline bool operator<(const value_t lhs, const value_t rhs) noexcept const auto l_index = static_cast(lhs); const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif } + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif } // namespace detail } // namespace nlohmann diff --git a/include/nlohmann/json.hpp b/include/nlohmann/json.hpp index fb0f5fc18..7a1030c76 100644 --- a/include/nlohmann/json.hpp +++ b/include/nlohmann/json.hpp @@ -1905,7 +1905,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -3538,7 +3537,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -3546,6 +3544,212 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ friend bool operator==(const_reference lhs, const_reference rhs) noexcept @@ -3554,71 +3758,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; - - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; - - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -3646,6 +3786,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs == rhs); } @@ -3671,76 +3815,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -3765,6 +3843,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -3790,6 +3872,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -3815,6 +3902,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -3835,6 +3926,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -5031,10 +5125,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 5993ce32a..30386c5e8 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -103,84 +103,6 @@ SOFTWARE. #include // uint8_t #include // string -namespace nlohmann -{ -namespace detail -{ -/////////////////////////// -// JSON type enumeration // -/////////////////////////// - -/*! -@brief the JSON type enumeration - -This enumeration collects the different JSON types. It is internally used to -distinguish the stored values, and the functions @ref basic_json::is_null(), -@ref basic_json::is_object(), @ref basic_json::is_array(), -@ref basic_json::is_string(), @ref basic_json::is_boolean(), -@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), -@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), -@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and -@ref basic_json::is_structured() rely on it. - -@note There are three enumeration entries (number_integer, number_unsigned, and -number_float), because the library distinguishes these three types for numbers: -@ref basic_json::number_unsigned_t is used for unsigned integers, -@ref basic_json::number_integer_t is used for signed integers, and -@ref basic_json::number_float_t is used for floating-point numbers or to -approximate integers which do not fit in the limits of their respective type. - -@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON -value with the default value for a given type - -@since version 1.0.0 -*/ -enum class value_t : std::uint8_t -{ - null, ///< null value - object, ///< object (unordered set of name/value pairs) - array, ///< array (ordered collection of values) - string, ///< string value - boolean, ///< boolean value - number_integer, ///< number value (signed integer) - number_unsigned, ///< number value (unsigned integer) - number_float, ///< number value (floating-point) - binary, ///< binary array (ordered collection of bytes) - discarded ///< discarded by the parser callback function -}; - -/*! -@brief comparison operator for JSON types - -Returns an ordering that is similar to Python: -- order: null < boolean < number < object < array < string < binary -- furthermore, each type is not smaller than itself -- discarded values are not comparable -- binary is represented as a b"" string in python and directly comparable to a - string; however, making a binary array directly comparable with a string would - be surprising behavior in a JSON file. - -@since version 1.0.0 -*/ -inline bool operator<(const value_t lhs, const value_t rhs) noexcept -{ - static constexpr std::array order = {{ - 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, - 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, - 6 /* binary */ - } - }; - - const auto l_index = static_cast(lhs); - const auto r_index = static_cast(rhs); - return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; -} -} // namespace detail -} // namespace nlohmann - -// #include - - // #include @@ -2342,6 +2264,12 @@ using is_detected_convertible = #define JSON_HAS_CPP_11 #endif +#ifdef __has_include + #if __has_include() + #include + #endif +#endif + #if !defined(JSON_HAS_FILESYSTEM) && !defined(JSON_HAS_EXPERIMENTAL_FILESYSTEM) #ifdef JSON_HAS_CPP_17 #if defined(__cpp_lib_filesystem) @@ -2403,14 +2331,31 @@ using is_detected_convertible = #endif #ifndef JSON_HAS_THREE_WAY_COMPARISON - #if defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L \ - && defined(__cpp_impl_three_way_comparison)&& __cpp_impl_three_way_comparison >= 201907L + #if defined(__cpp_impl_three_way_comparison) && __cpp_impl_three_way_comparison >= 201907L \ + && defined(__cpp_lib_three_way_comparison) && __cpp_lib_three_way_comparison >= 201907L #define JSON_HAS_THREE_WAY_COMPARISON 1 #else #define JSON_HAS_THREE_WAY_COMPARISON 0 #endif #endif +#ifndef JSON_HAS_RANGES + // ranges header shipping in GCC 11.1.0 (released 2021-04-27) has syntax error + #if defined(__GLIBCXX__) && __GLIBCXX__ == 20210427 + #define JSON_HAS_RANGES 0 + #elif defined(__cpp_lib_ranges) + #define JSON_HAS_RANGES 1 + #else + #define JSON_HAS_RANGES 0 + #endif +#endif + +#ifdef JSON_HAS_CPP_17 + #define JSON_INLINE_VARIABLE inline +#else + #define JSON_INLINE_VARIABLE +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else @@ -2735,6 +2680,117 @@ using is_detected_convertible = #define JSON_DIAGNOSTICS 0 #endif +#ifndef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + #define JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON 0 +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + #include // partial_ordering +#endif + +namespace nlohmann +{ +namespace detail +{ +/////////////////////////// +// JSON type enumeration // +/////////////////////////// + +/*! +@brief the JSON type enumeration + +This enumeration collects the different JSON types. It is internally used to +distinguish the stored values, and the functions @ref basic_json::is_null(), +@ref basic_json::is_object(), @ref basic_json::is_array(), +@ref basic_json::is_string(), @ref basic_json::is_boolean(), +@ref basic_json::is_number() (with @ref basic_json::is_number_integer(), +@ref basic_json::is_number_unsigned(), and @ref basic_json::is_number_float()), +@ref basic_json::is_discarded(), @ref basic_json::is_primitive(), and +@ref basic_json::is_structured() rely on it. + +@note There are three enumeration entries (number_integer, number_unsigned, and +number_float), because the library distinguishes these three types for numbers: +@ref basic_json::number_unsigned_t is used for unsigned integers, +@ref basic_json::number_integer_t is used for signed integers, and +@ref basic_json::number_float_t is used for floating-point numbers or to +approximate integers which do not fit in the limits of their respective type. + +@sa see @ref basic_json::basic_json(const value_t value_type) -- create a JSON +value with the default value for a given type + +@since version 1.0.0 +*/ +enum class value_t : std::uint8_t +{ + null, ///< null value + object, ///< object (unordered set of name/value pairs) + array, ///< array (ordered collection of values) + string, ///< string value + boolean, ///< boolean value + number_integer, ///< number value (signed integer) + number_unsigned, ///< number value (unsigned integer) + number_float, ///< number value (floating-point) + binary, ///< binary array (ordered collection of bytes) + discarded ///< discarded by the parser callback function +}; + +/*! +@brief comparison operator for JSON types + +Returns an ordering that is similar to Python: +- order: null < boolean < number < object < array < string < binary +- furthermore, each type is not smaller than itself +- discarded values are not comparable +- binary is represented as a b"" string in python and directly comparable to a + string; however, making a binary array directly comparable with a string would + be surprising behavior in a JSON file. + +@since version 1.0.0 +*/ +#if JSON_HAS_THREE_WAY_COMPARISON + inline std::partial_ordering operator<=>(const value_t lhs, const value_t rhs) noexcept // *NOPAD* +#else + inline bool operator<(const value_t lhs, const value_t rhs) noexcept +#endif +{ + static constexpr std::array order = {{ + 0 /* null */, 3 /* object */, 4 /* array */, 5 /* string */, + 1 /* boolean */, 2 /* integer */, 2 /* unsigned */, 2 /* float */, + 6 /* binary */ + } + }; + + const auto l_index = static_cast(lhs); + const auto r_index = static_cast(rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + if (l_index < order.size() && r_index < order.size()) + { + return order[l_index] <=> order[r_index]; // *NOPAD* + } + return std::partial_ordering::unordered; +#else + return l_index < order.size() && r_index < order.size() && order[l_index] < order[r_index]; +#endif +} + +// GCC selects the built-in operator< over an operator rewritten from +// a user-defined spaceship operator +// Clang, MSVC, and ICC select the rewritten candidate +// (see GCC bug https://gcc.gnu.org/bugzilla/show_bug.cgi?id=105200) +#if JSON_HAS_THREE_WAY_COMPARISON && defined(__GNUC__) +inline bool operator<(const value_t lhs, const value_t rhs) noexcept +{ + return std::is_lt(lhs <=> rhs); // *NOPAD* +} +#endif +} // namespace detail +} // namespace nlohmann + +// #include + + +// #include + namespace nlohmann { @@ -4133,7 +4189,7 @@ namespace nlohmann namespace detail { template -void from_json(const BasicJsonType& j, typename std::nullptr_t& n) +inline void from_json(const BasicJsonType& j, typename std::nullptr_t& n) { if (JSON_HEDLEY_UNLIKELY(!j.is_null())) { @@ -4180,7 +4236,7 @@ void get_arithmetic_value(const BasicJsonType& j, ArithmeticType& val) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) { if (JSON_HEDLEY_UNLIKELY(!j.is_boolean())) { @@ -4190,7 +4246,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::boolean_t& b) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::string_t& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4205,7 +4261,7 @@ template < std::is_assignable::value && !std::is_same::value && !is_json_ref::value, int > = 0 > -void from_json(const BasicJsonType& j, StringType& s) +inline void from_json(const BasicJsonType& j, StringType& s) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4216,26 +4272,26 @@ void from_json(const BasicJsonType& j, StringType& s) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_float_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_unsigned_t& val) { get_arithmetic_value(j, val); } template -void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::number_integer_t& val) { get_arithmetic_value(j, val); } template::value, int> = 0> -void from_json(const BasicJsonType& j, EnumType& e) +inline void from_json(const BasicJsonType& j, EnumType& e) { typename std::underlying_type::type val; get_arithmetic_value(j, val); @@ -4245,7 +4301,7 @@ void from_json(const BasicJsonType& j, EnumType& e) // forward_list doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::forward_list& l) +inline void from_json(const BasicJsonType& j, std::forward_list& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4262,7 +4318,7 @@ void from_json(const BasicJsonType& j, std::forward_list& l) // valarray doesn't have an insert method template::value, int> = 0> -void from_json(const BasicJsonType& j, std::valarray& l) +inline void from_json(const BasicJsonType& j, std::valarray& l) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4287,7 +4343,7 @@ auto from_json(const BasicJsonType& j, T (&arr)[N]) // NOLINT(cppcoreguidelines } template -void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, typename BasicJsonType::array_t& arr, priority_tag<3> /*unused*/) { arr = *j.template get_ptr(); } @@ -4331,8 +4387,8 @@ template::value, int> = 0> -void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, - priority_tag<0> /*unused*/) +inline void from_json_array_impl(const BasicJsonType& j, ConstructibleArrayType& arr, + priority_tag<0> /*unused*/) { using std::end; @@ -4389,7 +4445,7 @@ auto from_json(BasicJsonType&& j, identity_tag> tag) } template -void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) +inline void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) { if (JSON_HEDLEY_UNLIKELY(!j.is_binary())) { @@ -4401,7 +4457,7 @@ void from_json(const BasicJsonType& j, typename BasicJsonType::binary_t& bin) template::value, int> = 0> -void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) +inline void from_json(const BasicJsonType& j, ConstructibleObjectType& obj) { if (JSON_HEDLEY_UNLIKELY(!j.is_object())) { @@ -4433,7 +4489,7 @@ template < typename BasicJsonType, typename ArithmeticType, !std::is_same::value&& !std::is_same::value, int > = 0 > -void from_json(const BasicJsonType& j, ArithmeticType& val) +inline void from_json(const BasicJsonType& j, ArithmeticType& val) { switch (static_cast(j)) { @@ -4483,7 +4539,7 @@ std::pair from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::pair& p, priority_tag<1> /*unused*/) { p = from_json_tuple_impl(std::forward(j), identity_tag> {}, priority_tag<0> {}); } @@ -4495,7 +4551,7 @@ std::tuple from_json_tuple_impl(BasicJsonType&& j, identity_tag -void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) +inline void from_json_tuple_impl(BasicJsonType&& j, std::tuple& t, priority_tag<3> /*unused*/) { t = from_json_tuple_impl_base(std::forward(j), index_sequence_for {}); } @@ -4515,7 +4571,7 @@ auto from_json(BasicJsonType&& j, TupleRelated&& t) template < typename BasicJsonType, typename Key, typename Value, typename Compare, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::map& m) +inline void from_json(const BasicJsonType& j, std::map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4535,7 +4591,7 @@ void from_json(const BasicJsonType& j, std::map& template < typename BasicJsonType, typename Key, typename Value, typename Hash, typename KeyEqual, typename Allocator, typename = enable_if_t < !std::is_constructible < typename BasicJsonType::string_t, Key >::value >> -void from_json(const BasicJsonType& j, std::unordered_map& m) +inline void from_json(const BasicJsonType& j, std::unordered_map& m) { if (JSON_HEDLEY_UNLIKELY(!j.is_array())) { @@ -4554,7 +4610,7 @@ void from_json(const BasicJsonType& j, std::unordered_map -void from_json(const BasicJsonType& j, std_fs::path& p) +inline void from_json(const BasicJsonType& j, std_fs::path& p) { if (JSON_HEDLEY_UNLIKELY(!j.is_string())) { @@ -4576,13 +4632,18 @@ struct from_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `from_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& from_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& from_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann // #include @@ -4608,6 +4669,10 @@ constexpr const auto& from_json = detail::static_const::va #include // tuple_size, get, tuple_element #include // move +#if JSON_HAS_RANGES + #include // enable_borrowed_range +#endif + // #include // #include @@ -4629,14 +4694,14 @@ template class iteration_proxy_value public: using difference_type = std::ptrdiff_t; using value_type = iteration_proxy_value; - using pointer = value_type * ; - using reference = value_type & ; + using pointer = value_type *; + using reference = value_type &; using iterator_category = std::input_iterator_tag; using string_type = typename std::remove_cv< typename std::remove_reference().key() ) >::type >::type; private: /// the iterator - IteratorType anchor; + IteratorType anchor{}; /// an index for arrays (used to create key names) std::size_t array_index = 0; /// last stringified array index @@ -4644,15 +4709,30 @@ template class iteration_proxy_value /// a string representation of the array index mutable string_type array_index_str = "0"; /// an empty string (to return a reference for primitive values) - const string_type empty_str{}; + string_type empty_str{}; public: - explicit iteration_proxy_value(IteratorType it) noexcept + explicit iteration_proxy_value() = default; + explicit iteration_proxy_value(IteratorType it, std::size_t array_index_ = 0) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_default_constructible::value) : anchor(std::move(it)) + , array_index(array_index_) {} + iteration_proxy_value(iteration_proxy_value const&) = default; + iteration_proxy_value& operator=(iteration_proxy_value const&) = default; + // older GCCs are a bit fussy and require explicit noexcept specifiers on defaulted functions + iteration_proxy_value(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_constructible::value + && std::is_nothrow_move_constructible::value) = default; + iteration_proxy_value& operator=(iteration_proxy_value&&) + noexcept(std::is_nothrow_move_assignable::value + && std::is_nothrow_move_assignable::value) = default; + ~iteration_proxy_value() = default; + /// dereference operator (needed for range-based for) - iteration_proxy_value& operator*() + const iteration_proxy_value& operator*() const { return *this; } @@ -4666,6 +4746,14 @@ template class iteration_proxy_value return *this; } + iteration_proxy_value operator++(int)& // NOLINT(cert-dcl21-cpp) + { + auto tmp = iteration_proxy_value(anchor, array_index); + ++anchor; + ++array_index; + return tmp; + } + /// equality operator (needed for InputIterator) bool operator==(const iteration_proxy_value& o) const { @@ -4726,25 +4814,34 @@ template class iteration_proxy { private: /// the container to iterate - typename IteratorType::reference container; + typename IteratorType::pointer container = nullptr; public: + explicit iteration_proxy() = default; + /// construct iteration proxy from a container explicit iteration_proxy(typename IteratorType::reference cont) noexcept - : container(cont) {} + : container(&cont) {} + + iteration_proxy(iteration_proxy const&) = default; + iteration_proxy& operator=(iteration_proxy const&) = default; + iteration_proxy(iteration_proxy&&) noexcept = default; + iteration_proxy& operator=(iteration_proxy&&) noexcept = default; + ~iteration_proxy() = default; /// return iterator begin (needed for range-based for) - iteration_proxy_value begin() noexcept + iteration_proxy_value begin() const noexcept { - return iteration_proxy_value(container.begin()); + return iteration_proxy_value(container->begin()); } /// return iterator end (needed for range-based for) - iteration_proxy_value end() noexcept + iteration_proxy_value end() const noexcept { - return iteration_proxy_value(container.end()); + return iteration_proxy_value(container->end()); } }; + // Structured Bindings Support // For further reference see https://blog.tartanllama.xyz/structured-bindings/ // And see https://github.com/nlohmann/json/pull/1391 @@ -4792,6 +4889,11 @@ class tuple_element> #endif } // namespace std +#if JSON_HAS_RANGES + template + inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy> = true; +#endif + // #include // #include @@ -5051,55 +5153,55 @@ struct external_constructor template::value, int> = 0> -void to_json(BasicJsonType& j, T b) noexcept +inline void to_json(BasicJsonType& j, T b) noexcept { external_constructor::construct(j, b); } template::value, int> = 0> -void to_json(BasicJsonType& j, const CompatibleString& s) +inline void to_json(BasicJsonType& j, const CompatibleString& s) { external_constructor::construct(j, s); } template -void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) +inline void to_json(BasicJsonType& j, typename BasicJsonType::string_t&& s) { external_constructor::construct(j, std::move(s)); } template::value, int> = 0> -void to_json(BasicJsonType& j, FloatType val) noexcept +inline void to_json(BasicJsonType& j, FloatType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberUnsignedType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept +inline void to_json(BasicJsonType& j, CompatibleNumberIntegerType val) noexcept { external_constructor::construct(j, static_cast(val)); } template::value, int> = 0> -void to_json(BasicJsonType& j, EnumType e) noexcept +inline void to_json(BasicJsonType& j, EnumType e) noexcept { using underlying_type = typename std::underlying_type::type; external_constructor::construct(j, static_cast(e)); } template -void to_json(BasicJsonType& j, const std::vector& e) +inline void to_json(BasicJsonType& j, const std::vector& e) { external_constructor::construct(j, e); } @@ -5112,39 +5214,39 @@ template < typename BasicJsonType, typename CompatibleArrayType, !std::is_same::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleArrayType& arr) +inline void to_json(BasicJsonType& j, const CompatibleArrayType& arr) { external_constructor::construct(j, arr); } template -void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) +inline void to_json(BasicJsonType& j, const typename BasicJsonType::binary_t& bin) { external_constructor::construct(j, bin); } template::value, int> = 0> -void to_json(BasicJsonType& j, const std::valarray& arr) +inline void to_json(BasicJsonType& j, const std::valarray& arr) { external_constructor::construct(j, std::move(arr)); } template -void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) +inline void to_json(BasicJsonType& j, typename BasicJsonType::array_t&& arr) { external_constructor::construct(j, std::move(arr)); } template < typename BasicJsonType, typename CompatibleObjectType, enable_if_t < is_compatible_object_type::value&& !is_basic_json::value, int > = 0 > -void to_json(BasicJsonType& j, const CompatibleObjectType& obj) +inline void to_json(BasicJsonType& j, const CompatibleObjectType& obj) { external_constructor::construct(j, obj); } template -void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) +inline void to_json(BasicJsonType& j, typename BasicJsonType::object_t&& obj) { external_constructor::construct(j, std::move(obj)); } @@ -5154,13 +5256,13 @@ template < enable_if_t < !std::is_constructible::value, // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) int > = 0 > -void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) +inline void to_json(BasicJsonType& j, const T(&arr)[N]) // NOLINT(cppcoreguidelines-avoid-c-arrays,hicpp-avoid-c-arrays,modernize-avoid-c-arrays) { external_constructor::construct(j, arr); } template < typename BasicJsonType, typename T1, typename T2, enable_if_t < std::is_constructible::value&& std::is_constructible::value, int > = 0 > -void to_json(BasicJsonType& j, const std::pair& p) +inline void to_json(BasicJsonType& j, const std::pair& p) { j = { p.first, p.second }; } @@ -5168,26 +5270,26 @@ void to_json(BasicJsonType& j, const std::pair& p) // for https://github.com/nlohmann/json/pull/1134 template>::value, int> = 0> -void to_json(BasicJsonType& j, const T& b) +inline void to_json(BasicJsonType& j, const T& b) { j = { {b.key(), b.value()} }; } template -void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) +inline void to_json_tuple_impl(BasicJsonType& j, const Tuple& t, index_sequence /*unused*/) { j = { std::get(t)... }; } template::value, int > = 0> -void to_json(BasicJsonType& j, const T& t) +inline void to_json(BasicJsonType& j, const T& t) { to_json_tuple_impl(j, t, make_index_sequence::value> {}); } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM template -void to_json(BasicJsonType& j, const std_fs::path& p) +inline void to_json(BasicJsonType& j, const std_fs::path& p) { j = p.string(); } @@ -5204,13 +5306,18 @@ struct to_json_fn }; } // namespace detail +#ifndef JSON_HAS_CPP_17 /// namespace to hold default `to_json` function /// to see why this is required: /// http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2015/n4381.html namespace // NOLINT(cert-dcl59-cpp,fuchsia-header-anon-namespaces,google-build-namespaces) { -constexpr const auto& to_json = detail::static_const::value; // NOLINT(misc-definitions-in-headers) +#endif +JSON_INLINE_VARIABLE constexpr const auto& to_json = // NOLINT(misc-definitions-in-headers) + detail::static_const::value; +#ifndef JSON_HAS_CPP_17 } // namespace +#endif } // namespace nlohmann // #include @@ -12155,9 +12262,12 @@ class iter_impl // NOLINT(cppcoreguidelines-special-member-functions,hicpp-speci // make sure BasicJsonType is basic_json or const basic_json static_assert(is_basic_json::type>::value, "iter_impl only accepts (const) basic_json"); + // superficial check for the LegacyBidirectionalIterator named requirement + static_assert(std::is_base_of::value + && std::is_base_of::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 @@ -20065,7 +20175,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec detail::negation>, detail::negation>, detail::negation>>, - #if defined(JSON_HAS_CPP_17) && (defined(__GNUC__) || (defined(_MSC_VER) && _MSC_VER >= 1910 && _MSC_VER <= 1914)) detail::negation>, #endif @@ -21698,7 +21807,6 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @} - public: ////////////////////////////////////////// // lexicographical comparison operators // ////////////////////////////////////////// @@ -21706,6 +21814,212 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @name lexicographical comparison operators /// @{ + // note parentheses around operands are necessary; see + // https://github.com/nlohmann/json/issues/1530 +#define JSON_IMPLEMENT_OPERATOR(op, null_result, unordered_result, default_result) \ + const auto lhs_type = lhs.type(); \ + const auto rhs_type = rhs.type(); \ + \ + if (lhs_type == rhs_type) /* NOLINT(readability/braces) */ \ + { \ + switch (lhs_type) \ + { \ + case value_t::array: \ + return (*lhs.m_value.array) op (*rhs.m_value.array); \ + \ + case value_t::object: \ + return (*lhs.m_value.object) op (*rhs.m_value.object); \ + \ + case value_t::null: \ + return (null_result); \ + \ + case value_t::string: \ + return (*lhs.m_value.string) op (*rhs.m_value.string); \ + \ + case value_t::boolean: \ + return (lhs.m_value.boolean) op (rhs.m_value.boolean); \ + \ + case value_t::number_integer: \ + return (lhs.m_value.number_integer) op (rhs.m_value.number_integer); \ + \ + case value_t::number_unsigned: \ + return (lhs.m_value.number_unsigned) op (rhs.m_value.number_unsigned); \ + \ + case value_t::number_float: \ + return (lhs.m_value.number_float) op (rhs.m_value.number_float); \ + \ + case value_t::binary: \ + return (*lhs.m_value.binary) op (*rhs.m_value.binary); \ + \ + case value_t::discarded: \ + default: \ + return (unordered_result); \ + } \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_integer) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_integer); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_float; \ + } \ + else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_float op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) \ + { \ + return static_cast(lhs.m_value.number_unsigned) op rhs.m_value.number_integer; \ + } \ + else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) \ + { \ + return lhs.m_value.number_integer op static_cast(rhs.m_value.number_unsigned); \ + } \ + else if(compares_unordered(lhs, rhs))\ + {\ + return (unordered_result);\ + }\ + \ + return (default_result); + + JSON_PRIVATE_UNLESS_TESTED: + // returns true if: + // - any operand is NaN and the other operand is of number type + // - any operand is discarded + // in legacy mode, discarded values are considered ordered if + // an operation is computed as an odd number of inverses of others + static bool compares_unordered(const_reference lhs, const_reference rhs, bool inverse = false) noexcept + { + if ((lhs.is_number_float() && std::isnan(lhs.m_value.number_float) && rhs.is_number()) + || (rhs.is_number_float() && std::isnan(rhs.m_value.number_float) && lhs.is_number())) + { + return true; + } +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + return (lhs.is_discarded() || rhs.is_discarded()) && !inverse; +#else + static_cast(inverse); + return lhs.is_discarded() || rhs.is_discarded(); +#endif + } + + private: + bool compares_unordered(const_reference rhs, bool inverse = false) const noexcept + { + return compares_unordered(*this, rhs, inverse); + } + + public: +#if JSON_HAS_THREE_WAY_COMPARISON + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + bool operator==(const_reference rhs) const noexcept + { +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wfloat-equal" +#endif + const_reference lhs = *this; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } + + /// @brief comparison: equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ + template + requires std::is_scalar_v + bool operator==(ScalarType rhs) const noexcept + { + return *this == basic_json(rhs); + } + + /// @brief comparison: not equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ + bool operator!=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !operator==(rhs); + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + std::partial_ordering operator<=>(const_reference rhs) const noexcept // *NOPAD* + { + const_reference lhs = *this; + // default_result is used if we cannot compare values. In that case, + // we compare types. + JSON_IMPLEMENT_OPERATOR(<=>, // *NOPAD* + std::partial_ordering::equivalent, + std::partial_ordering::unordered, + lhs_type <=> rhs_type) // *NOPAD* + } + + /// @brief comparison: 3-way + /// @sa https://json.nlohmann.me/api/basic_json/operator_spaceship/ + template + requires std::is_scalar_v + std::partial_ordering operator<=>(ScalarType rhs) const noexcept // *NOPAD* + { + return *this <=> basic_json(rhs); // *NOPAD* + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + // all operators that are computed as an odd number of inverses of others + // need to be overloaded to emulate the legacy comparison behavior + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator<=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(rhs < *this); + } + + /// @brief comparison: less than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ + template + requires std::is_scalar_v + bool operator<=(ScalarType rhs) const noexcept + { + return *this <= basic_json(rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + JSON_HEDLEY_DEPRECATED_FOR(3.11.0, undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON) + bool operator>=(const_reference rhs) const noexcept + { + if (compares_unordered(rhs, true)) + { + return false; + } + return !(*this < rhs); + } + + /// @brief comparison: greater than or equal + /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ + template + requires std::is_scalar_v + bool operator>=(ScalarType rhs) const noexcept + { + return *this >= basic_json(rhs); + } +#endif +#else /// @brief comparison: equal /// @sa https://json.nlohmann.me/api/basic_json/operator_eq/ friend bool operator==(const_reference lhs, const_reference rhs) noexcept @@ -21714,71 +22028,7 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec #pragma GCC diagnostic push #pragma GCC diagnostic ignored "-Wfloat-equal" #endif - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - return *lhs.m_value.array == *rhs.m_value.array; - - case value_t::object: - return *lhs.m_value.object == *rhs.m_value.object; - - case value_t::null: - return true; - - case value_t::string: - return *lhs.m_value.string == *rhs.m_value.string; - - case value_t::boolean: - return lhs.m_value.boolean == rhs.m_value.boolean; - - case value_t::number_integer: - return lhs.m_value.number_integer == rhs.m_value.number_integer; - - case value_t::number_unsigned: - return lhs.m_value.number_unsigned == rhs.m_value.number_unsigned; - - case value_t::number_float: - return lhs.m_value.number_float == rhs.m_value.number_float; - - case value_t::binary: - return *lhs.m_value.binary == *rhs.m_value.binary; - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float == static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) == rhs.m_value.number_integer; - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer == static_cast(rhs.m_value.number_unsigned); - } - - return false; + JSON_IMPLEMENT_OPERATOR( ==, true, false, false) #ifdef __GNUC__ #pragma GCC diagnostic pop #endif @@ -21806,6 +22056,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ne/ friend bool operator!=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs == rhs); } @@ -21831,76 +22085,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_lt/ friend bool operator<(const_reference lhs, const_reference rhs) noexcept { - const auto lhs_type = lhs.type(); - const auto rhs_type = rhs.type(); - - if (lhs_type == rhs_type) - { - switch (lhs_type) - { - case value_t::array: - // note parentheses are necessary, see - // https://github.com/nlohmann/json/issues/1530 - return (*lhs.m_value.array) < (*rhs.m_value.array); - - case value_t::object: - return (*lhs.m_value.object) < (*rhs.m_value.object); - - case value_t::null: - return false; - - case value_t::string: - return (*lhs.m_value.string) < (*rhs.m_value.string); - - case value_t::boolean: - return (lhs.m_value.boolean) < (rhs.m_value.boolean); - - case value_t::number_integer: - return (lhs.m_value.number_integer) < (rhs.m_value.number_integer); - - case value_t::number_unsigned: - return (lhs.m_value.number_unsigned) < (rhs.m_value.number_unsigned); - - case value_t::number_float: - return (lhs.m_value.number_float) < (rhs.m_value.number_float); - - case value_t::binary: - return (*lhs.m_value.binary) < (*rhs.m_value.binary); - - case value_t::discarded: - default: - return false; - } - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_integer) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_integer) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_integer); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_float) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_float; - } - else if (lhs_type == value_t::number_float && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_float < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_integer && rhs_type == value_t::number_unsigned) - { - return lhs.m_value.number_integer < static_cast(rhs.m_value.number_unsigned); - } - else if (lhs_type == value_t::number_unsigned && rhs_type == value_t::number_integer) - { - return static_cast(lhs.m_value.number_unsigned) < rhs.m_value.number_integer; - } - - // We only reach this line if we cannot compare values. In that case, + // default_result is used if we cannot compare values. In that case, // we compare types. Note we have to call the operator explicitly, // because MSVC has problems otherwise. - return operator<(lhs_type, rhs_type); + JSON_IMPLEMENT_OPERATOR( <, false, false, operator<(lhs_type, rhs_type)) } /// @brief comparison: less than @@ -21925,6 +22113,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_le/ friend bool operator<=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(rhs < lhs); } @@ -21950,6 +22142,11 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_gt/ friend bool operator>(const_reference lhs, const_reference rhs) noexcept { + // double inverse + if (compares_unordered(lhs, rhs)) + { + return false; + } return !(lhs <= rhs); } @@ -21975,6 +22172,10 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec /// @sa https://json.nlohmann.me/api/basic_json/operator_ge/ friend bool operator>=(const_reference lhs, const_reference rhs) noexcept { + if (compares_unordered(lhs, rhs, true)) + { + return false; + } return !(lhs < rhs); } @@ -21995,6 +22196,9 @@ class basic_json // NOLINT(cppcoreguidelines-special-member-functions,hicpp-spec { return basic_json(lhs) >= rhs; } +#endif + +#undef JSON_IMPLEMENT_OPERATOR /// @} @@ -23191,10 +23395,14 @@ struct less< ::nlohmann::detail::value_t> // do not remove the space after '<', @brief compare two value_t enum values @since version 3.0.0 */ - bool operator()(nlohmann::detail::value_t lhs, - nlohmann::detail::value_t rhs) const noexcept + bool operator()(::nlohmann::detail::value_t lhs, + ::nlohmann::detail::value_t rhs) const noexcept { - return nlohmann::detail::operator<(lhs, rhs); +#if JSON_HAS_THREE_WAY_COMPARISON + return std::is_lt(lhs <=> rhs); // *NOPAD* +#else + return ::nlohmann::detail::operator<(lhs, rhs); +#endif } }; @@ -23248,6 +23456,7 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef NLOHMANN_BASIC_JSON_TPL #undef JSON_EXPLICIT #undef NLOHMANN_CAN_CALL_STD_FUNC_IMPL +#undef JSON_INLINE_VARIABLE #undef JSON_NO_UNIQUE_ADDRESS #ifndef JSON_TEST_KEEP_MACROS @@ -23260,6 +23469,8 @@ inline nlohmann::json::json_pointer operator "" _json_pointer(const char* s, std #undef JSON_HAS_FILESYSTEM #undef JSON_HAS_EXPERIMENTAL_FILESYSTEM #undef JSON_HAS_THREE_WAY_COMPARISON + #undef JSON_HAS_RANGES + #undef JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON #endif // #include diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 2f6409c57..010c4950e 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -123,6 +123,15 @@ foreach(file ${files}) json_test_add_test_for(${file} MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force}) endforeach() +# test legacy comparison of discarded values +json_test_set_test_options(test-comparison_legacy + COMPILE_DEFINITIONS JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON=1 +) +json_test_add_test_for(src/unit-comparison.cpp + NAME test-comparison_legacy + MAIN test_main CXX_STANDARDS ${test_cxx_standards} ${test_force} +) + # *DO NOT* use json_test_set_test_options() below this line ############################################################################# diff --git a/tests/src/unit-class_parser.cpp b/tests/src/unit-class_parser.cpp index 95a958d1f..3d2677394 100644 --- a/tests/src/unit-class_parser.cpp +++ b/tests/src/unit-class_parser.cpp @@ -1454,17 +1454,17 @@ TEST_CASE("parser class") SECTION("filter specific element") { - json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { // filter all number(2) elements - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_object == json({{"bar", {{"baz", 1}}}})); - json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t event, const json & j) noexcept { - return j != json(2); + return event != json::parse_event_t::value || j != json(2); }); CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); diff --git a/tests/src/unit-comparison.cpp b/tests/src/unit-comparison.cpp index 6c94add60..f310a70d1 100644 --- a/tests/src/unit-comparison.cpp +++ b/tests/src/unit-comparison.cpp @@ -27,11 +27,49 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" +#define JSON_TESTS_PRIVATE #include using nlohmann::json; +#if JSON_HAS_THREE_WAY_COMPARISON +// this can be replaced with the doctest stl extension header in version 2.5 +namespace doctest +{ +template<> struct StringMaker +{ + static String convert(const std::partial_ordering& order) + { + if (order == std::partial_ordering::less) + { + return "std::partial_ordering::less"; + } + if (order == std::partial_ordering::equivalent) + { + return "std::partial_ordering::equivalent"; + } + if (order == std::partial_ordering::greater) + { + return "std::partial_ordering::greater"; + } + if (order == std::partial_ordering::unordered) + { + return "std::partial_ordering::unordered"; + } + return "{?}"; + } +}; +} // namespace doctest +#endif + namespace { // helper function to check std::less @@ -45,6 +83,27 @@ bool f(A a, B b, U u = U()) TEST_CASE("lexicographical comparison operators") { + constexpr auto f_ = false; + constexpr auto _t = true; + constexpr auto nan = std::numeric_limits::quiet_NaN(); +#if JSON_HAS_THREE_WAY_COMPARISON + constexpr auto lt = std::partial_ordering::less; + constexpr auto gt = std::partial_ordering::greater; + constexpr auto eq = std::partial_ordering::equivalent; + constexpr auto un = std::partial_ordering::unordered; +#endif + +#if JSON_HAS_THREE_WAY_COMPARISON + INFO("using 3-way comparison"); +#endif + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + INFO("using legacy comparison"); +#endif + + //REQUIRE(std::numeric_limits::has_quiet_NaN); + REQUIRE(std::isnan(nan)); + SECTION("types") { std::vector j_types = @@ -57,97 +116,268 @@ TEST_CASE("lexicographical comparison operators") json::value_t::object, json::value_t::array, json::value_t::string, - json::value_t::binary + json::value_t::binary, + json::value_t::discarded + }; + + std::vector> expected_lt = + { + //0 1 2 3 4 5 6 7 8 9 + {f_, _t, _t, _t, _t, _t, _t, _t, _t, f_}, // 0 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, f_}, // 1 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 2 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 3 + {f_, f_, f_, f_, f_, _t, _t, _t, _t, f_}, // 4 + {f_, f_, f_, f_, f_, f_, _t, _t, _t, f_}, // 5 + {f_, f_, f_, f_, f_, f_, f_, _t, _t, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 }; SECTION("comparison: less") { - std::vector> expected = - { - {false, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, true, true, true, true}, - {false, false, false, false, false, false, true, true, true}, - {false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false} - }; - + REQUIRE(expected_lt.size() == j_types.size()); for (size_t i = 0; i < j_types.size(); ++i) { + REQUIRE(expected_lt[i].size() == j_types.size()); for (size_t j = 0; j < j_types.size(); ++j) { CAPTURE(i) CAPTURE(j) // check precomputed values - CHECK(operator<(j_types[i], j_types[j]) == expected[i][j]); - CHECK(f(j_types[i], j_types[j]) == expected[i][j]); +#if JSON_HAS_THREE_WAY_COMPARISON + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + CHECK((j_types[i] < j_types[j]) == expected_lt[i][j]); +#else + CHECK(operator<(j_types[i], j_types[j]) == expected_lt[i][j]); +#endif + CHECK(f(j_types[i], j_types[j]) == expected_lt[i][j]); } } } +#if JSON_HAS_THREE_WAY_COMPARISON + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + SECTION("comparison: 3-way") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 + {eq, lt, lt, lt, lt, lt, lt, lt, lt, un}, // 0 + {gt, eq, lt, lt, lt, lt, lt, lt, lt, un}, // 1 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 2 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 3 + {gt, gt, eq, eq, eq, lt, lt, lt, lt, un}, // 4 + {gt, gt, gt, gt, gt, eq, lt, lt, lt, un}, // 5 + {gt, gt, gt, gt, gt, gt, eq, lt, lt, un}, // 6 + {gt, gt, gt, gt, gt, gt, gt, eq, lt, un}, // 7 + {gt, gt, gt, gt, gt, gt, gt, gt, eq, un}, // 8 + {un, un, un, un, un, un, un, un, un, un}, // 9 + }; + + // check expected partial_ordering against expected boolean + REQUIRE(expected.size() == expected_lt.size()); + for (size_t i = 0; i < expected.size(); ++i) + { + REQUIRE(expected[i].size() == expected_lt[i].size()); + for (size_t j = 0; j < expected[i].size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]); + } + } + + // check 3-way comparison against expected partial_ordering + REQUIRE(expected.size() == j_types.size()); + for (size_t i = 0; i < j_types.size(); ++i) + { + REQUIRE(expected[i].size() == j_types.size()); + for (size_t j = 0; j < j_types.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK((j_types[i] <=> j_types[j]) == expected[i][j]); // *NOPAD* + } + } + } +#endif } SECTION("values") { json j_values = { - nullptr, nullptr, - -17, 42, - 8u, 13u, - 3.14159, 23.42, - "foo", "bar", - true, false, - {1, 2, 3}, {"one", "two", "three"}, - {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, - json::binary({1, 2, 3}), json::binary({1, 2, 4}) + nullptr, nullptr, // 0 1 + -17, 42, // 2 3 + 8u, 13u, // 4 5 + 3.14159, 23.42, // 6 7 + nan, nan, // 8 9 + "foo", "bar", // 10 11 + true, false, // 12 13 + {1, 2, 3}, {"one", "two", "three"}, // 14 15 + {{"first", 1}, {"second", 2}}, {{"a", "A"}, {"b", {"B"}}}, // 16 17 + json::binary({1, 2, 3}), json::binary({1, 2, 4}), // 18 19 + json(json::value_t::discarded), json(json::value_t::discarded) // 20 21 }; - SECTION("comparison: equal") + std::vector> expected_eq = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0 + {_t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1 + {f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2 + {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3 + {f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4 + {f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5 + {f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 + }; + + std::vector> expected_lt = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 0 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_}, // 1 + {f_, f_, f_, _t, _t, _t, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 3 + {f_, f_, f_, _t, f_, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 4 + {f_, f_, f_, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 5 + {f_, f_, f_, _t, _t, _t, f_, _t, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 6 + {f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 7 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 8 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 11 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 12 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, f_, _t, _t, _t, _t, _t, _t, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, _t, f_, f_, _t, _t, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_, _t, _t, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, _t, _t, _t, f_, _t, _t, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 + }; + + SECTION("compares unordered") { std::vector> expected = { - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {true, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, false}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true} + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, _t, _t}, // 19 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 20 + {_t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t, _t}, // 21 }; + // check if two values compare unordered as expected + REQUIRE(expected.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected[i].size() == j_values.size()); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(json::compares_unordered(j_values[i], j_values[j]) == expected[i][j]); + } + } + } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + SECTION("compares unordered (inverse)") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 0 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 1 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 2 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 3 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 4 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 5 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 6 + {f_, f_, f_, f_, f_, f_, f_, f_, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 7 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 8 + {f_, f_, _t, _t, _t, _t, _t, _t, _t, _t, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 9 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 10 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 11 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 12 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 13 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 14 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 15 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 16 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 17 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 18 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 19 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 20 + {f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_, f_}, // 21 + }; + + // check that two values compare unordered as expected (with legacy-mode enabled) + REQUIRE(expected.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) + { + REQUIRE(expected[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) CAPTURE(j_values[i]) CAPTURE(j_values[j]) - // check precomputed values - CHECK( (j_values[i] == j_values[j]) == expected[i][j] ); + CHECK(json::compares_unordered(j_values[i], j_values[j], true) == expected[i][j]); } } + } +#endif - // comparison with discarded elements - json j_discarded(json::value_t::discarded); - for (const auto& v : j_values) + SECTION("comparison: equal") + { + // check that two values compare equal + REQUIRE(expected_eq.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) { - CHECK( (v == j_discarded) == false); - CHECK( (j_discarded == v) == false); - CHECK( (j_discarded == j_discarded) == false); + REQUIRE(expected_eq[i].size() == j_values.size()); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK((j_values[i] == j_values[j]) == expected_eq[i][j]); + } } // compare with null pointer @@ -158,121 +388,229 @@ TEST_CASE("lexicographical comparison operators") SECTION("comparison: not equal") { + // check that two values compare unequal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] != j_values[j]) == !(j_values[i] == j_values[j]) ); + + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] != j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of equal + CHECK((j_values[i] != j_values[j]) == !(j_values[i] == j_values[j])); + } } } // compare with null pointer json j_null; - CHECK( (j_null != nullptr) == false); - CHECK( (nullptr != j_null) == false); - CHECK( (j_null != nullptr) == !(j_null == nullptr)); - CHECK( (nullptr != j_null) == !(nullptr == j_null)); + CHECK((j_null != nullptr) == false); + CHECK((nullptr != j_null) == false); + CHECK((j_null != nullptr) == !(j_null == nullptr)); + CHECK((nullptr != j_null) == !(nullptr == j_null)); } SECTION("comparison: less") { - std::vector> expected = - { - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true, true}, - {false, false, false, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, true, true, false, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, true, false, false, false, false, true, true, false, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, false, false, false, false, false, false, false, true, true}, - {false, false, true, true, true, true, true, true, true, true, false, false, true, true, true, true, true, true}, - {false, false, true, true, true, true, true, true, true, true, true, false, true, true, true, true, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, false, false, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, false, false, true, true}, - {false, false, false, false, false, false, false, false, true, true, false, false, true, true, true, false, true, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, true}, - {false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false, false} - }; - + // check that two values compare less than as expected + REQUIRE(expected_lt.size() == j_values.size()); for (size_t i = 0; i < j_values.size(); ++i) { + REQUIRE(expected_lt[i].size() == j_values.size()); for (size_t j = 0; j < j_values.size(); ++j) { - // Skip comparing indicies 12 and 13, and 13 and 12 in C++20 pending fix - // See issue #3207 -#if defined(JSON_HAS_CPP_20) || JSON_HAS_THREE_WAY_COMPARISON - if ((i == 12 && j == 13) || (i == 13 && j == 12)) - { - continue; - } -#endif CAPTURE(i) CAPTURE(j) - CAPTURE(j_values[i]) - CAPTURE(j_values[j]) - // check precomputed values - CHECK( (j_values[i] < j_values[j]) == expected[i][j] ); + CHECK((j_values[i] < j_values[j]) == expected_lt[i][j]); } } - - // comparison with discarded elements - json j_discarded(json::value_t::discarded); - for (size_t i = 0; i < j_values.size(); ++i) - { - CAPTURE(i) - CHECK( (j_values[i] < j_discarded) == false); - CHECK( (j_discarded < j_values[i]) == false); - CHECK( (j_discarded < j_discarded) == false); - } } SECTION("comparison: less than or equal equal") { + // check that two values compare less than or equal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] <= j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than with the operand order reversed + CHECK((j_values[i] <= j_values[j]) == !(j_values[j] < j_values[i])); + } } } } SECTION("comparison: greater than") { + // check that two values compare greater than as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] > j_values[j]) == (j_values[j] < j_values[i]) ); + if (json::compares_unordered(j_values[i], j_values[j])) + { + // if two values compare unordered, + // check that the boolean comparison result is always false + CHECK_FALSE(j_values[i] > j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than or equal which is defined as + // the inverse of less than with the operand order reversed + CHECK((j_values[i] > j_values[j]) == !(j_values[i] <= j_values[j])); + CHECK((j_values[i] > j_values[j]) == !!(j_values[j] < j_values[i])); + } } } } SECTION("comparison: greater than or equal") { + // check that two values compare greater than or equal as expected for (size_t i = 0; i < j_values.size(); ++i) { for (size_t j = 0; j < j_values.size(); ++j) { CAPTURE(i) CAPTURE(j) - // check definition - CHECK( (j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j]) ); + if (json::compares_unordered(j_values[i], j_values[j], true)) + { + // if two values compare unordered, + // check that the boolean result is always false + CHECK_FALSE(j_values[i] >= j_values[j]); + } + else + { + // otherwise, check that they compare according to their definition + // as the inverse of less than + CHECK((j_values[i] >= j_values[j]) == !(j_values[i] < j_values[j])); + } } } } + +#if JSON_HAS_THREE_WAY_COMPARISON + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + SECTION("comparison: 3-way") + { + std::vector> expected = + { + //0 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 + {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 0 + {eq, eq, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, un, un}, // 1 + {gt, gt, eq, lt, lt, lt, lt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 2 + {gt, gt, gt, eq, gt, gt, gt, gt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 3 + {gt, gt, gt, lt, eq, lt, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 4 + {gt, gt, gt, lt, gt, eq, gt, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 5 + {gt, gt, gt, lt, lt, lt, eq, lt, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 6 + {gt, gt, gt, lt, gt, gt, gt, eq, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 7 + {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 8 + {gt, gt, un, un, un, un, un, un, un, un, lt, lt, gt, gt, lt, lt, lt, lt, lt, lt, un, un}, // 9 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, gt, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 10 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, eq, gt, gt, gt, gt, gt, gt, lt, lt, un, un}, // 11 + {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, gt, lt, lt, lt, lt, lt, lt, un, un}, // 12 + {gt, gt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, lt, eq, lt, lt, lt, lt, lt, lt, un, un}, // 13 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, eq, lt, gt, gt, lt, lt, un, un}, // 14 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, gt, eq, gt, gt, lt, lt, un, un}, // 15 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, eq, gt, lt, lt, un, un}, // 16 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, lt, lt, gt, gt, lt, lt, lt, eq, lt, lt, un, un}, // 17 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, lt, un, un}, // 18 + {gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, gt, eq, un, un}, // 19 + {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 20 + {un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un, un}, // 21 + }; + + // check expected partial_ordering against expected booleans + REQUIRE(expected.size() == expected_eq.size()); + REQUIRE(expected.size() == expected_lt.size()); + for (size_t i = 0; i < expected.size(); ++i) + { + REQUIRE(expected[i].size() == expected_eq[i].size()); + REQUIRE(expected[i].size() == expected_lt[i].size()); + for (size_t j = 0; j < expected[i].size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK(std::is_eq(expected[i][j]) == expected_eq[i][j]); + CHECK(std::is_lt(expected[i][j]) == expected_lt[i][j]); + if (std::is_gt(expected[i][j])) + { + CHECK((!expected_eq[i][j] && !expected_lt[i][j])); + } + } + } + + // check that two values compare according to their expected ordering + REQUIRE(expected.size() == j_values.size()); + for (size_t i = 0; i < j_values.size(); ++i) + { + REQUIRE(expected[i].size() == j_values.size()); + for (size_t j = 0; j < j_values.size(); ++j) + { + CAPTURE(i) + CAPTURE(j) + CHECK((j_values[i] <=> j_values[j]) == expected[i][j]); // *NOPAD* + } + } + } +#endif } + +#if JSON_USE_LEGACY_DISCARDED_VALUE_COMPARISON + SECTION("parser callback regression") + { + SECTION("filter specific element") + { + const auto* s_object = R"( + { + "foo": 2, + "bar": { + "baz": 1 + } + } + )"; + const auto* s_array = R"( + [1,2,[3,4,5],4,5] + )"; + + json j_object = json::parse(s_object, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + // filter all number(2) elements + return j != json(2); + }); + + CHECK (j_object == json({{"bar", {{"baz", 1}}}})); + + json j_array = json::parse(s_array, [](int /*unused*/, json::parse_event_t /*unused*/, const json & j) noexcept + { + return j != json(2); + }); + + CHECK (j_array == json({1, {3, 4, 5}, 4, 5})); + } + } +#endif } diff --git a/tests/src/unit-conversions.cpp b/tests/src/unit-conversions.cpp index c61b249c2..bfa9bb98f 100644 --- a/tests/src/unit-conversions.cpp +++ b/tests/src/unit-conversions.cpp @@ -27,6 +27,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" #define JSON_TESTS_PRIVATE @@ -1582,12 +1589,4 @@ TEST_CASE("JSON to enum mapping") } } -#ifdef JSON_HAS_CPP_17 - #undef JSON_HAS_CPP_17 -#endif - -#ifdef JSON_HAS_CPP_14 - #undef JSON_HAS_CPP_14 -#endif - DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-items.cpp b/tests/src/unit-items.cpp index fa40f46d9..64d92b59e 100644 --- a/tests/src/unit-items.cpp +++ b/tests/src/unit-items.cpp @@ -80,7 +80,7 @@ TEST_CASE("iterator_wrapper") json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -226,7 +226,7 @@ TEST_CASE("iterator_wrapper") const json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -361,7 +361,7 @@ TEST_CASE("iterator_wrapper") json j = { "A", "B" }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -507,7 +507,7 @@ TEST_CASE("iterator_wrapper") const json j = { "A", "B" }; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -624,7 +624,7 @@ TEST_CASE("iterator_wrapper") json j = 1; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -693,7 +693,7 @@ TEST_CASE("iterator_wrapper") const json j = 1; int counter = 1; - for (auto& i : json::iterator_wrapper(j)) + for (auto& i : json::iterator_wrapper(j)) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -777,7 +777,7 @@ TEST_CASE("items()") json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -939,7 +939,7 @@ TEST_CASE("items()") const json j = { {"A", 1}, {"B", 2} }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1074,7 +1074,7 @@ TEST_CASE("items()") json j = { "A", "B" }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1220,7 +1220,7 @@ TEST_CASE("items()") const json j = { "A", "B" }; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { switch (counter++) { @@ -1337,7 +1337,7 @@ TEST_CASE("items()") json j = 1; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -1406,7 +1406,7 @@ TEST_CASE("items()") const json j = 1; int counter = 1; - for (auto& i : j.items()) + for (auto& i : j.items()) // NOLINT(readability-qualified-auto) { ++counter; CHECK(i.key() == ""); @@ -1448,13 +1448,5 @@ TEST_CASE("items()") } } -#ifdef JSON_HAS_CPP_17 - #undef JSON_HAS_CPP_17 -#endif - -#ifdef JSON_HAS_CPP_14 - #undef JSON_HAS_CPP_14 -#endif - DOCTEST_GCC_SUPPRESS_WARNING_POP DOCTEST_CLANG_SUPPRESS_WARNING_POP diff --git a/tests/src/unit-iterators2.cpp b/tests/src/unit-iterators2.cpp index 976d0bcda..b9c418c16 100644 --- a/tests/src/unit-iterators2.cpp +++ b/tests/src/unit-iterators2.cpp @@ -27,11 +27,23 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" #include using nlohmann::json; +#if JSON_HAS_RANGES + #include + #include +#endif + TEST_CASE("iterators 2") { SECTION("iterator comparisons") @@ -881,4 +893,101 @@ TEST_CASE("iterators 2") } } } + + +#if JSON_HAS_RANGES + // JSON_HAS_CPP_20 (do not remove; see note at top of file) + SECTION("ranges") + { + SECTION("concepts") + { + using nlohmann::detail::iteration_proxy_value; + CHECK(std::bidirectional_iterator); + CHECK(std::input_iterator>); + + CHECK(std::is_same>::value); + CHECK(std::ranges::bidirectional_range); + + using nlohmann::detail::iteration_proxy; + using items_type = decltype(std::declval().items()); + CHECK(std::is_same>::value); + CHECK(std::is_same, std::ranges::iterator_t>::value); + CHECK(std::ranges::input_range); + } + + // libstdc++ algorithms don't work with Clang 15 (04/2022) +#if !defined(__clang__) || (defined(__clang__) && defined(__GLIBCXX__)) + SECTION("algorithms") + { + SECTION("copy") + { + json j{"foo", "bar"}; + auto j_copied = json::array(); + + std::ranges::copy(j, std::back_inserter(j_copied)); + + CHECK(j == j_copied); + } + + SECTION("find_if") + { + json j{1, 3, 2, 4}; + auto j_even = json::array(); + +#if JSON_USE_IMPLICIT_CONVERSIONS + auto it = std::ranges::find_if(j, [](int v) noexcept + { + return (v % 2) == 0; + }); +#else + auto it = std::ranges::find_if(j, [](const json & j) noexcept + { + int v; + j.get_to(v); + return (v % 2) == 0; + }); +#endif + + CHECK(*it == 2); + } + } +#endif + + // libstdc++ views don't work with Clang 15 (04/2022) + // libc++ hides limited ranges implementation behind guard macro +#if !(defined(__clang__) && (defined(__GLIBCXX__) || defined(_LIBCPP_HAS_NO_INCOMPLETE_RANGES))) + SECTION("views") + { + SECTION("reverse") + { + json j{1, 2, 3, 4, 5}; + json j_expected{5, 4, 3, 2, 1}; + + auto reversed = j | std::views::reverse; + CHECK(reversed == j_expected); + } + + SECTION("transform") + { + json j + { + { "a_key", "a_value"}, + { "b_key", "b_value"}, + { "c_key", "c_value"}, + }; + json j_expected{"a_key", "b_key", "c_key"}; + + auto transformed = j.items() | std::views::transform([](const auto & item) + { + return item.key(); + }); + auto j_transformed = json::array(); + std::ranges::copy(transformed, std::back_inserter(j_transformed)); + + CHECK(j_transformed == j_expected); + } + } +#endif + } +#endif } diff --git a/tests/src/unit-regression2.cpp b/tests/src/unit-regression2.cpp index 04cbac679..3ee234eee 100644 --- a/tests/src/unit-regression2.cpp +++ b/tests/src/unit-regression2.cpp @@ -27,6 +27,13 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. */ +// cmake/test.cmake selects the C++ standard versions with which to build a +// unit test based on the presence of JSON_HAS_CPP_ macros. +// When using macros that are only defined for particular versions of the standard +// (e.g., JSON_HAS_FILESYSTEM for C++17 and up), please mention the corresponding +// version macro in a comment close by, like this: +// JSON_HAS_CPP_ (do not remove; see note at top of file) + #include "doctest_compatibility.h" // for some reason including this after the json header leads to linker errors with VS 2017... @@ -48,7 +55,6 @@ using ordered_json = nlohmann::ordered_json; #endif #if JSON_HAS_EXPERIMENTAL_FILESYSTEM -// JSON_HAS_CPP_17 (magic keyword; do not remove) #include namespace nlohmann::detail { @@ -788,6 +794,7 @@ TEST_CASE("regression tests 2") } #if JSON_HAS_FILESYSTEM || JSON_HAS_EXPERIMENTAL_FILESYSTEM + // JSON_HAS_CPP_17 (do not remove; see note at top of file) SECTION("issue #3070 - Version 3.10.3 breaks backward-compatibility with 3.10.2 ") { nlohmann::detail::std_fs::path text_path("/tmp/text.txt");