diff --git a/include/nlohmann/detail/iterators/iter_impl.hpp b/include/nlohmann/detail/iterators/iter_impl.hpp index cdcdaff1f2..81c61b3e09 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 9994b364c4..a75614de1e 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 ae91d79540..01123f7ccf 100644 --- a/include/nlohmann/detail/macro_scope.hpp +++ b/include/nlohmann/detail/macro_scope.hpp @@ -114,12 +114,26 @@ #define JSON_HAS_THREE_WAY_COMPARISON 0 #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 + #endif +#endif + +#ifndef JSON_HAS_RANGES + #define JSON_HAS_RANGES 0 +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else #define JSON_NO_UNIQUE_ADDRESS #endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push diff --git a/single_include/nlohmann/json.hpp b/single_include/nlohmann/json.hpp index 4b0457dd74..a5fc384f78 100644 --- a/single_include/nlohmann/json.hpp +++ b/single_include/nlohmann/json.hpp @@ -2341,12 +2341,26 @@ using is_detected_convertible = #define JSON_HAS_THREE_WAY_COMPARISON 0 #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 + #endif +#endif + +#ifndef JSON_HAS_RANGES + #define JSON_HAS_RANGES 0 +#endif + #if JSON_HEDLEY_HAS_ATTRIBUTE(no_unique_address) #define JSON_NO_UNIQUE_ADDRESS [[no_unique_address]] #else #define JSON_NO_UNIQUE_ADDRESS #endif + // disable documentation warnings on clang #if defined(__clang__) #pragma clang diagnostic push @@ -4641,6 +4655,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 @@ -4662,14 +4680,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 @@ -4677,15 +4695,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; } @@ -4699,6 +4732,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 { @@ -4759,25 +4800,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 @@ -4825,6 +4875,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 @@ -12105,9 +12160,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/tests/src/unit-items.cpp b/tests/src/unit-items.cpp index fa40f46d9a..b219bc27eb 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() == ""); diff --git a/tests/src/unit-iterators2.cpp b/tests/src/unit-iterators2.cpp index 976d0bcda3..36b28cc180 100644 --- a/tests/src/unit-iterators2.cpp +++ b/tests/src/unit-iterators2.cpp @@ -32,6 +32,12 @@ SOFTWARE. #include using nlohmann::json; +#if JSON_HAS_RANGES + // JSON_HAS_CPP_20 (magic keyword; do not remove) + #include + #include +#endif + TEST_CASE("iterators 2") { SECTION("iterator comparisons") @@ -881,4 +887,100 @@ TEST_CASE("iterators 2") } } } + + +#if JSON_HAS_RANGES + 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 }