Skip to content

Commit

Permalink
Fix iterators to meet (more) std::ranges requirements
Browse files Browse the repository at this point in the history
Fixes nlohmann#3130.
Related discussion: nlohmann#3408
  • Loading branch information
falbrechtskirchinger committed May 20, 2022
1 parent f186c75 commit 0732cec
Show file tree
Hide file tree
Showing 6 changed files with 232 additions and 46 deletions.
5 changes: 4 additions & 1 deletion include/nlohmann/detail/iterators/iter_impl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
65 changes: 53 additions & 12 deletions include/nlohmann/detail/iterators/iteration_proxy.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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>

Expand All @@ -25,30 +29,45 @@ 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
mutable std::size_t array_index_last = 0;
/// 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;
}
Expand All @@ -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
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
6 changes: 2 additions & 4 deletions include/nlohmann/detail/macro_scope.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
76 changes: 59 additions & 17 deletions single_include/nlohmann/json.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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>
Expand All @@ -4681,30 +4683,45 @@ 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
mutable std::size_t array_index_last = 0;
/// 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;
}
Expand All @@ -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
{
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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>
Expand Down Expand Up @@ -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
Expand Down
Loading

0 comments on commit 0732cec

Please sign in to comment.