Skip to content
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

Remove parametrization of end iterators #2168

Merged
merged 6 commits into from
Jan 9, 2018

Conversation

frozar
Copy link
Contributor

@frozar frozar commented Dec 23, 2017

I add an octree test to check the behavior of current iterator with a 'for loop' (which is the main usage in reality I think). This test curently fails. In the second for loop, I got this message:

test_octree_iterator: /home/frozar/wk/pcl_repo/octree/include/pcl/octree/octree_iterator.h:190:
pcl::octree::OctreeNode* pcl::octree::OctreeIteratorBase<OctreeT>::getCurrentOctreeNode() const [with OctreeT = pcl::octree::OctreeBase<int>]:
Assertion `current_state_!=0' failed.

I think that the iterator goes over every node of depth egal to 0 and 1, as expected, but it is never equal to the end iterator. If you look at the source:

bool operator==(const OctreeIteratorBase& other) const
{
return (this == &other) ||
((octree_ == other.octree_) &&
(max_octree_depth_ == other.max_octree_depth_) &&
((current_state_ == other.current_state_) || // end state case
(current_state_ && other.current_state_ && // null dereference protection
(current_state_->key_ == other.current_state_->key_))));
}

to be equal, two iterators must have the same max_octree_depth value. I'm not sure checking that is fair. When you look at the creation of the end iterator:

const Iterator end (unsigned int max_depth_arg = 0u)
{
return Iterator (this, max_depth_arg? max_depth_arg : this->octree_depth_ , NULL);
};

the default max_depth_arg is 0, so max depth of the end iterator will be the depth of the octree by default. So in a for loop, when one specifies a max depth inferior to the depth of an octree, the exit condition will never match.

Proposition

  • delete the check max_octree_depth_ == other.max_octree_depth_
  • delete the max_depth_arg for the end iterators, it is not necessary

@SergioRAgostinho
Copy link
Member

I don't agree with what you're proposing but maybe I'm failing to see the point. Maybe it's better to elaborate on the changes you want by actually implementing them, plus the necessary adjustments to the units tests.

Based simply on what your wrote it's still unclear why you don't think it's "fair". When you use octree.begin(depth) you need to use the appropriate octree.end(depth). I believe that if the begin iterator is parameterized on a desired depth, the end iterator must also be.

Iterators with different max depths don't perform traversal the same way, therefore the max depth needs to be taken into account during the equivalence comparison. If something is really equal, it means that if you apply the same operations to both "copies", they need to end up in the same state after those operations. With your proposal, you can no longer ensure this.

@frozar
Copy link
Contributor Author

frozar commented Dec 24, 2017

When you use octree.begin(depth) you need to use the appropriate octree.end(depth).

This is the point of my proposition. Before the recent change of the iterator implementation, whatever the depth you use to walk over an octree, it was possible to reach the exit condition with an octree.end(), without specifying the depth accordingly to the begin iterator.

My preference is to allow the use of octree.end() whatever the initial depth of the begin iterator. What I propose it to sacrifice the checking of max_octree_depth_ during comparison, and I know you're right without this check, you cannot know if two iterators are really equivalent.

I don't want to care about the initial depth of an iterator to parametrise the end iterator accordingly. Maybe it's possible to adjust the comparison operator to get both behavior:

  • say two iterators are equivalent,
  • say an iterator is in its end state whatever the max_octree_depth_ is.

I'll do another proposition.

@frozar
Copy link
Contributor Author

frozar commented Dec 24, 2017

In my last commit, I modify the comparison of iterator to preserve/get the behaviors I was talking about.

Of course after this change, on the unit test './test/octree/test_octree_iterator' I get the following failures:

[  FAILED  ] 6 tests, listed below:
[  FAILED  ] OctreeBaseBeginEndIteratorsTest.End
[  FAILED  ] OctreeBaseBeginEndIteratorsTest.LeafEnd
[  FAILED  ] OctreeBaseBeginEndIteratorsTest.DepthEnd
[  FAILED  ] OctreeBaseBeginEndIteratorsTest.BreadthEnd
[  FAILED  ] OctreePointCloudAdjacencyBeginEndIteratorsTest.DepthEnd
[  FAILED  ] OctreePointCloudAdjacencyBeginEndIteratorsTest.LeafEnd

as expected.

@SergioRAgostinho : Before to go further, do you agree with the spirit of this comparison operator?

@SergioRAgostinho
Copy link
Member

SergioRAgostinho commented Dec 24, 2017

Iterators with different max depths don't perform traversal the same way, therefore the max depth needs to be taken into account during the equivalence comparison. If something is really equal, it means that if you apply the same operations to both "copies", they need to end up in the same state after those operations. With your proposal, you can no longer ensure this.

This is the main point I'm not willing to violate/wave. If you come up with a solution which respects this constraint and doesn't require octree.end() to be parameterized on the depth, then I'm totally in favor of merging it.

I understand passing a parameter to end() doesn't feel normal, but passing a parameter to begin() is also non existent in the standard library.

@SergioRAgostinho
Copy link
Member

SergioRAgostinho commented Dec 24, 2017

@SergioRAgostinho : Before to go further, do you agree with the spirit of this comparison operator?

Just noticed you asked me this directly. Can you change the failing units tests to showcase the new logic you implemented with your modifications i.e., modify them so that they all pass.

Edit: In theory your comparison logic should do it. You'll need to remove all the end methods I added, to stop accepting a depth parameter.

@SergioRAgostinho SergioRAgostinho added the needs: more work Specify why not closed/merged yet label Dec 24, 2017
@taketwo
Copy link
Member

taketwo commented Dec 25, 2017

My preference is to allow the use of octree.end() whatever the initial depth of the begin iterator.

As far as I can see, our iterators are not decrementable. If they were, end() would absolutely need to parametrized. But in currently situation it is maybe okay to make it parameterless. Just my two cents.

@frozar
Copy link
Contributor Author

frozar commented Dec 25, 2017

Ok, in this pull request, we have:

  • add a new octree test,
  • update the operator==() to be able to use the end iterator method without any max depth parameter,
  • delete the max_octree_depth_ of the end() iterator methods,
  • update the previous octree test to respect the new logic: end iterators don't take care of the max depth parameter.

Ready for a review.

@frozar frozar changed the title WIP: Add another octree test [OCTREE] end iterator without parametrisation Dec 25, 2017
@SergioRAgostinho SergioRAgostinho added needs: code review Specify why not closed/merged yet and removed needs: more work Specify why not closed/merged yet labels Dec 26, 2017

EXPECT_NE (it_m_1, it_m_2);
EXPECT_EQ (it_m_2, it_m); // tree depth is 2
EXPECT_NE (it_m_1, it_m_b_1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You should leave the test in which you verify that the end iterators from different octrees are not the same.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These tests concern iterators from different depth and differnet octree. The test about iterators from different octrees is preserved above.

Copy link
Contributor Author

@frozar frozar left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wait for your feedback.

@@ -258,16 +258,6 @@ TEST_F (OctreeBaseBeginEndIteratorsTest, End)
EXPECT_EQ (it_a_1, it_a_2);
EXPECT_NE (it_a_1, it_b);
EXPECT_NE (it_a_2, it_b);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@SergioRAgostinho: This is the test about iterator from different octrees.

@frozar
Copy link
Contributor Author

frozar commented Dec 27, 2017

A rebase has been done.

((!(current_state_) && !(other.current_state_)) || // end state case
((max_octree_depth_ == other.max_octree_depth_) &&
(current_state_ && other.current_state_ && // null dereference protection
(current_state_->key_ == other.current_state_->key_)))));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Am I the only one who finds this return statement extremely convoluted and confusing? What about something like this? I suppose the compiler will produce the same assembly anyway.

if (this == &other)  // same object
  return true;
if (octree_ != other.octree_)  // refer to different octrees
  return false;
if (!current_state_ && !other.current_state_)  // both are end iterators
  return true;
if (max_octree_depth_ == other.max_octree_depth_ &&
    current_state_ && other.current_state_ &&  // null dereference protection
    current_state_->key_ == other.current_state_.key_)
  return true;
return false;

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I should test it, but I completely agree with you, your proposition is obviously more readable @taketwo 👍

@SergioRAgostinho SergioRAgostinho added changelog: API break Meta-information for changelog generation and removed needs: code review Specify why not closed/merged yet labels Jan 9, 2018
@SergioRAgostinho SergioRAgostinho merged commit 491819b into PointCloudLibrary:master Jan 9, 2018
@frozar frozar deleted the add_test_octree branch January 9, 2018 12:59
@taketwo
Copy link
Member

taketwo commented Jan 27, 2018

BTW, why are we breaking API without first deprecating it? I think we should keep parametrized versions for several releases.

@SergioRAgostinho
Copy link
Member

SergioRAgostinho commented Jan 27, 2018

IIRC I introduced the parameterized version in this development stage. It never saw a public release. Or did I overlook something?

Edit: Yep, just confirmed it. ABI Laboratory is reporting 100% between this current version and 1.8.1 for libpcl_octree. Supposedly it compiled the master branch on 2018-01-04 12:55, already after we pushed this.

@taketwo
Copy link
Member

taketwo commented Jan 27, 2018

But parametrized versions have been around like... forever. Here is how OctreeBase looked in 1.8.1: https://github.com/PointCloudLibrary/pcl/blob/pcl-1.8.1/octree/include/pcl/octree/octree_base.h, it has parameters.

@SergioRAgostinho
Copy link
Member

SergioRAgostinho commented Jan 27, 2018

The end iterators don't have it. Only the begin ones. This PR just removed the parametrization I introduced in the end iterators. Are you mentioning something else?

Even so, the ABI report is completely clean. There was no report generated yet, this was merged Jan 9 and the report is Jan 4

@taketwo
Copy link
Member

taketwo commented Jan 27, 2018

Sorry, apparently I need a second coffee this morning. Everything is alright indeed.

@taketwo
Copy link
Member

taketwo commented Jan 27, 2018

Only... why do we have the "Breaks API" tag then?

@SergioRAgostinho
Copy link
Member

Well... if you see it individually it breaks the API of the "current snapshot", the same way the parametrization I introduced before broke the ABI. When all changes are combined together, it no longer does. So the question is what is the policy we want to apply in these cases?

I'm perfectly OK with both options (having/not having the API breakage tag) and we can always create more tags to provide more information in cases as special as this one.

@taketwo
Copy link
Member

taketwo commented Jan 27, 2018

I think that the tags are supposed to help us put together a changelog. And in the changelog we list cumulative changes with respect to the previously released version. Whatever happened during the development stage does not really matter. E.g. if we introduced a bug and the fixed it, the world does not need to know about this, right? Same for API changes that were introduced and then removed.

@SergioRAgostinho
Copy link
Member

I think that the tags are supposed to help us put together a changelog

I'll tag with this in mind from now on.

@taketwo taketwo removed the changelog: API break Meta-information for changelog generation label Jan 27, 2018
@taketwo taketwo changed the title [OCTREE] end iterator without parametrisation Remove parametrization of end iterators Sep 2, 2018
@taketwo taketwo added changelog: API break Meta-information for changelog generation module: octree labels Sep 2, 2018
@SergioRAgostinho
Copy link
Member

I'm removing the breaks API tag, because it didn't affect the API between 1.8.1 and 1.9.0.

@SergioRAgostinho SergioRAgostinho removed the changelog: API break Meta-information for changelog generation label Sep 25, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants