Fix iterators to meet (more) std::ranges requirements

Fixes #3130.
Related discussion: #3408
This commit is contained in:
Florian Albrechtskirchinger 2022-04-18 17:39:27 +02:00
parent 529e4100c5
commit fe2c4d7777
No known key found for this signature in database
GPG Key ID: 19618CE9B2D4BE6D
6 changed files with 232 additions and 46 deletions

View File

@ -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<typename std::remove_const<BasicJsonType>::type>::value,
"iter_impl only accepts (const) basic_json");
// superficial check for the LegacyBidirectionalIterator named requirement
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value
&& std::is_base_of<std::bidirectional_iterator_tag, typename array_t::iterator::iterator_category>::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

View File

@ -6,6 +6,10 @@
#include <tuple> // tuple_size, get, tuple_element
#include <utility> // move
#if JSON_HAS_RANGES
#include <ranges> // enable_borrowed_range
#endif
#include <nlohmann/detail/meta/type_traits.hpp>
#include <nlohmann/detail/value_t.hpp>
@ -25,14 +29,14 @@ template<typename IteratorType> 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<decltype( std::declval<IteratorType>().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<typename IteratorType> 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<IteratorType>::value
&& std::is_nothrow_default_constructible<string_type>::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<IteratorType>::value
&& std::is_nothrow_move_constructible<string_type>::value) = default;
iteration_proxy_value& operator=(iteration_proxy_value&&)
noexcept(std::is_nothrow_move_assignable<IteratorType>::value
&& std::is_nothrow_move_assignable<string_type>::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<typename IteratorType> 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<typename IteratorType> 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<IteratorType> begin() noexcept
iteration_proxy_value<IteratorType> begin() const noexcept
{
return iteration_proxy_value<IteratorType>(container.begin());
return iteration_proxy_value<IteratorType>(container->begin());
}
/// return iterator end (needed for range-based for)
iteration_proxy_value<IteratorType> end() noexcept
iteration_proxy_value<IteratorType> end() const noexcept
{
return iteration_proxy_value<IteratorType>(container.end());
return iteration_proxy_value<IteratorType>(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<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
#pragma clang diagnostic pop
#endif
} // namespace std
#if JSON_HAS_RANGES
template <typename IteratorType>
inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;
#endif

View File

@ -118,13 +118,11 @@
#define JSON_HAS_RANGES 0
#elif defined(__cpp_lib_ranges)
#define JSON_HAS_RANGES 1
#else
#define JSON_HAS_RANGES 0
#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

View File

@ -2345,13 +2345,11 @@ using is_detected_convertible =
#define JSON_HAS_RANGES 0
#elif defined(__cpp_lib_ranges)
#define JSON_HAS_RANGES 1
#else
#define JSON_HAS_RANGES 0
#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
@ -4660,6 +4658,10 @@ constexpr const auto& from_json = detail::static_const<detail::from_json_fn>::va
#include <tuple> // tuple_size, get, tuple_element
#include <utility> // move
#if JSON_HAS_RANGES
#include <ranges> // enable_borrowed_range
#endif
// #include <nlohmann/detail/meta/type_traits.hpp>
// #include <nlohmann/detail/value_t.hpp>
@ -4681,14 +4683,14 @@ template<typename IteratorType> 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<decltype( std::declval<IteratorType>().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
@ -4696,15 +4698,30 @@ template<typename IteratorType> 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<IteratorType>::value
&& std::is_nothrow_default_constructible<string_type>::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<IteratorType>::value
&& std::is_nothrow_move_constructible<string_type>::value) = default;
iteration_proxy_value& operator=(iteration_proxy_value&&)
noexcept(std::is_nothrow_move_assignable<IteratorType>::value
&& std::is_nothrow_move_assignable<string_type>::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;
}
@ -4718,6 +4735,14 @@ template<typename IteratorType> 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
{
@ -4778,25 +4803,34 @@ template<typename IteratorType> 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<IteratorType> begin() noexcept
iteration_proxy_value<IteratorType> begin() const noexcept
{
return iteration_proxy_value<IteratorType>(container.begin());
return iteration_proxy_value<IteratorType>(container->begin());
}
/// return iterator end (needed for range-based for)
iteration_proxy_value<IteratorType> end() noexcept
iteration_proxy_value<IteratorType> end() const noexcept
{
return iteration_proxy_value<IteratorType>(container.end());
return iteration_proxy_value<IteratorType>(container->end());
}
};
// Structured Bindings Support
// For further reference see https://blog.tartanllama.xyz/structured-bindings/
// And see https://github.com/nlohmann/json/pull/1391
@ -4844,6 +4878,11 @@ class tuple_element<N, ::nlohmann::detail::iteration_proxy_value<IteratorType >>
#endif
} // namespace std
#if JSON_HAS_RANGES
template <typename IteratorType>
inline constexpr bool ::std::ranges::enable_borrowed_range<::nlohmann::detail::iteration_proxy<IteratorType>> = true;
#endif
// #include <nlohmann/detail/meta/cpp_future.hpp>
// #include <nlohmann/detail/meta/type_traits.hpp>
@ -12197,9 +12236,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<typename std::remove_const<BasicJsonType>::type>::value,
"iter_impl only accepts (const) basic_json");
// superficial check for the LegacyBidirectionalIterator named requirement
static_assert(std::is_base_of<std::bidirectional_iterator_tag, std::bidirectional_iterator_tag>::value
&& std::is_base_of<std::bidirectional_iterator_tag, typename array_t::iterator::iterator_category>::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

View File

@ -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() == "");

View File

@ -32,6 +32,12 @@ SOFTWARE.
#include <nlohmann/json.hpp>
using nlohmann::json;
#if JSON_HAS_RANGES
// JSON_HAS_CPP_20 (magic keyword; do not remove)
#include <algorithm>
#include <ranges>
#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<json::iterator>);
CHECK(std::input_iterator<iteration_proxy_value<json::iterator>>);
CHECK(std::is_same<json::iterator, std::ranges::iterator_t<json>>::value);
CHECK(std::ranges::bidirectional_range<json>);
using nlohmann::detail::iteration_proxy;
using items_type = decltype(std::declval<json&>().items());
CHECK(std::is_same<items_type, iteration_proxy<json::iterator>>::value);
CHECK(std::is_same<iteration_proxy_value<json::iterator>, std::ranges::iterator_t<items_type>>::value);
CHECK(std::ranges::input_range<items_type>);
}
// 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
}