-
-
Notifications
You must be signed in to change notification settings - Fork 6.8k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Issues with the arithmetic in iterator and reverse iterator #593
Comments
Actually I found some more issues during the fix up. The iter_impl was defined as a random access iterator (https://github.com/nlohmann/json/blob/develop/src/json.hpp#L7994), but a couple of lines later, the iterator category is then changed to bidirectional (https://github.com/nlohmann/json/blob/develop/src/json.hpp#L8018). I tried to fix this, but it introduced some fail the test cases, e.g. So I dig it up and found that the json reverse iterator is overriding all the operators, while the implementation of the Here is the implementation in llvm: template <class _Iter1, class _Iter2>
inline _LIBCPP_INLINE_VISIBILITY
typename reverse_iterator<_Iter1>::difference_type
operator-(const reverse_iterator<_Iter1>& __x, const reverse_iterator<_Iter2>& __y)
{
return __y.base() - __x.base();
} In addition, the Personally, I would recommend removing all the operator override in the reverse iterator, since the template class Reference: std::reverse_iterator |
Sorry, I made a mistake in the previous message. As for the reverse iterator, I can see why the operators are override. But still three operators were implemented in an incorrect way, which are |
It took me quite a while to get the iterators "working". I expect that there are better ways to implement them, and I am not surprised that there are issues. I am grateful for any hints how to improve the situation. |
I could make them compiled. But some of the cases are failing. I think it's the problem of the test code. For example, I can see that iterators of object types are not able to compare, but some test cases were still using And the other cases are related to the reverse iterator, could be fixed easily. |
Oh, just misunderstood your last message. I have an idea of implementing the reverse iterator, instead of inheriting the |
Sure. I'll have these bugs related to arithmetic operators fixed today, then I'll see what I can do to improve the design, and also #550. Pleasure to help. |
I did a code walk today, and wondering if I could improve the iterator code structure. Currently the iterator has three internal iterators, which looked very redundant at the first glance. So I was trying to merge the three internal iterators into one type. Another issue is that the iterator category, actually should be consistent with the iterator class in the internal container. For example, if the container is It turned out that, none of these improvement is possible, because the My last recommendation is to change the iterator category to random accessible tag, because in this tag, running functions like |
I'm sorry that I won't find the time to properly answer here or have a deeper look at the PRs. Sorry about this - I shall get back to this next week. |
No worries. Take your time. |
Regarding #593 (comment): I agree to paragraph 1-4. The idea is that all JSON values have the same type ( In the 5th paragraph, you propose to change the behavior of the iterator so
|
I understand. How about the iterator category tag? Do you think we should change it to random access tag? Even if we don't change the implementation, we can still use the random accessible tag. All we need to do is removing the test cases that are using random access operations on object types. (i.e. https://github.com/nlohmann/json/blob/develop/test/src/unit-capacity.cpp#L327) |
Your mentioned line is CHECK(std::distance(j.rbegin(), j.rend()) == j.size()); for an object |
Correct. Because |
I am puzzled - why did it work before? |
Or to clarify: I would like JSON objects to behave just like |
It's still working now. We haven't change the tag to random access tag yet. |
We have to choose one tag, either bidirectional or random access. Currently we are using bidirectional, and it works as |
I'll try to make this clearer. The iterator has been using the bidirectional tag all the time. Even though it inherits the random accessible iterator, the tag is changed to bidirectional a few lines later. When I tried to change the tag to random access, many bugs occurred, but most of them got resolved in the PR#595. I am still keeping this tag as bidirectional, because if we use random access, If we don't change the current implementation, the pros and cons in bidirectional and random access tag ares:
|
Actually there is a ambiguity on the definition of the object type. If you believe the objects should have no ordering at all, then |
Why would std::distance be linear on array types if we use std::vector internally? |
If the iterators are bidirectional instead of random access, then |
Gregmarr is right, if the tag is bidirectional, the |
Sorry @HenryRLee for my ongoing confusion. I am unsure about this. Any further opinion is greatly appreciated. |
Personally, I still think adding Having overloading the |
I think the iterator should remain bidirectional, we don't want to lose correctness on JSON objects. IIRC, there is a way to access the underlying value ( |
This commit changes the iterator category to andom_access_iterator and allows offsets and subscript operators for object iterators.
@HenryRLee Thanks for the offer, but I already gave it a try, see c77a0be. It would be great if you could have a look at the changes. |
It's incorrect to make this iterator class look like a random-access iterator -- in some cases it is (vector), but in others it's not (map). You'll create a correctness issue, and could cause major performance regressions if the STL starts choosing algorithms tuned for O(1) access that turns out to be O(n). For example, std::sort requires random-access in order to achieve O(n log n), but if you give it iterators that actually have O(n) traversal when it is expecting O(1), you could end up with really bad performance. This iterator needs to be the greatest-common-denominator of the choices, and that is bidirectional. |
Hi @jaredgrubb Correct me if I am wrong. As far as I can see, there are no performance regressions in any functions in The only problem is that user may be expecting more when they sees the tag as a random-access tag. It's the same issue when running |
I agree with @jaredgrubb, as I wrote a few months ago , we should not modify this. Bidirectional is correct in every cases, not optimal that's true, so what? If a user wants to perform some algorithms on a JSON array, they just convert it into a I'm sorry but I don't see how this proposed solution could be beneficial to the library |
@HenryRLee, I have to disagree. You're right that there's no direct performance regression with your patch (the things that worked before continue to work just fine, and maybe even faster), but you're opting the iterator into new things that it may not be suited for. Look, if you review the requirements for a random-access iterator (eg, at http://en.cppreference.com/w/cpp/concept/RandomAccessIterator), then you will see that the '+=' has a note requiring 'complexity is constant'. The json::iterator clearly does not fulfill that, and therefore must not be labelled a random-access iterator. |
I agree that it is ugly/incorrect to call an interator "random-access" when it does not satisfy all of its requirements. I also agree that using a bidirectional iterator as least common denominator could be a solution for this. But the nature of the JSON container makes life more difficult, because we need to find a way to find a common interface for differing underlying containers such as That is why I like the idea of having an iterator that combines the best of all worlds. Constant runtime for arrays, and also the same interface for objects. I agree that it may feel awkward/terrible/wrong for people familiar with the iterator requirements, but helpful for others that, for instance, call I'm looking forward to reading more here, and I hope that we can find a way to achieve a nice interface for a JSON container. |
Several things here: It's good for an interface to be easy to use, but also hard to misuse. About Even with C++17 searchers, using
I forgot that algorithms just SFINAE on the iterator tag, nevermind that. Anyway I stand by my first point, that we should not defeat the purpose of iterator machinery (and thus the whole Standard Library design) If a user desires to perform an algorithm that requires I don't remember seeing an issue about a support of such an algorithm yet (though I need to look closely at issues to confirm that), so crippling correctness to solve an issue that nobody has mentioned yet is bound to create some. That is a pretty big deal IMO |
I reverted the changes for now. |
I agree that misrepresenting the iterator tag can be very bad. If this checking could be done at runtime, so that if it's on an array, the iterator is random access, but on an object it's bidirectional, that would be okay, but I'm pretty sure that the machinery doesn't work that way, and it's all static typing. |
You are right, it's compile-time only. |
OK, the longer I think about this, the more I am convinced that we should not change the code to Question is: is there anything to be changed in the code to close this issue once and for all? |
I think we have to find a solution to bind the iterator type dynamically. It would be difficult but not impossible. Note that if we are strict to the least common denominator, bidirectional is not the one, because user may choose a forward list as an array type, or even an unordered map as an object (although we don't support it yet). Having said that, this issue can be closed. Perhaps we need to change the template argument of the iterator to bidirectional and update all the documents, since I remember they were all random access tag all the time. |
Actually yes, IIRC we inherit from There is code that relies on I don't see anything concerning the iterator category. |
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions. |
Would this be sufficient to switch to bidirectional iterators and close this issue? @ -3618,7 +3618,7 @@ This class implements a both iterators (iterator and const_iterator) for the
@since version 1.0.0, simplified in version 2.0.9
*/
template<typename BasicJsonType>
- class iter_impl : public std::iterator<std::random_access_iterator_tag, BasicJsonType>
+ class iter_impl : public std::iterator<std::bidirectional_iterator_tag, BasicJsonType>
{
/// allow basic_json to access private members
friend iter_impl |
You can also remove the |
Thanks everyone. I shall close this issue once the CI succeeds. |
Hi, I just found that the iterator class hasn't override the
n + iterator
operator, which is a requirement in the random access iterator type.More specifically, this operator is missing:
The failing code is:
The text was updated successfully, but these errors were encountered: