Skip to content

Deleted iterator copy constructor/assignment and finding #272

@wdconinc

Description

@wdconinc

I suspect there are good reasons to delete the iterators in python/template/macros/iterator/iterator.jinja2, but it makes searching through a collection a bit more cumbersome at times. I am wondering if there are ways in which some of the choices on iterator copy constructors and assignment operators deletion can be relaxed, or am I missing something I should be using instead?

The following are attempts at finding the first electron in an MCParticlesCollection. I am using gcc-11 with -std=gnu++20.

Using find_if to get an iterator to the first electron

    const auto mc_first_electron = std::find_if(
      mcparts.begin(),
      mcparts.end(),
      [](const auto& p){ return p.getPDG() == 11; });

This fails because MCParticleCollectionIterator has a deleted copy constructor which is required (likely) to return a copy of the internal iterator inside std::find_if.

Using a simple (old-style) iterator loop

If we can't copy an iterator, then let's avoid the copy.

    auto mc_first_electron = mcparts.end();
    for (auto p = mcparts.begin(); p != mcparts.end(); p++) {
      if (p->getPDG() == 11) {
        mc_first_electron = p;
        break;
      }
    }

This fails because iterator post-increment is not implemented. Ok, easy enough to change (and arguably better though unlikely that an iterator takes up so much memory this matters here; still, it is good to instill best practices).

    auto mc_first_electron = mcparts.end();
    for (auto p = mcparts.begin(); p != mcparts.end(); ++p) {
      if (p->getPDG() == 11) {
        mc_first_electron = p;
        break;
      }
    }

This still fails but now because we cannot assign p to mc_first_electron since the assignment operator for iterators is deleted. Ok, we can avoid this assignment too, by avoiding a locally scoped iterator.

    auto mc_first_electron = mcparts.begin();
    for (; mc_first_electron != mcparts.end(); ++mc_first_electron) {
      if (mc_first_electron->getPDG() == 11) {
        break;
      }
    }

Ah, we have something that compiles.

Checking that we actually found something

After the search, we want to make sure we found something (and return if not).

    if (mc_first_electron == mcparts.end()) {
      debug() << "No electron found" << endmsg;
      return StatusCode::FAILURE;
    }

This now fails again because operator== isn't defined for iterators. We already used operator!= above, so we end up with this:

    if (!(mc_first_electron != mcparts.end())) {
      debug() << "No electron found" << endmsg;
      return StatusCode::FAILURE;
    }

Ultimately, in a c++20 world,...

When I started off, I had hopes of being able to use the following compact syntax:

    auto is_electron = [](const auto& p){ return p.getPDG() == 11; };
    for (const auto& e: mcparts | std::views::filter(is_electron)) {
      // stuff
    }

This does not compile (unsurprisingly) due to the missing operator| on collections. Implementing this doesn't seem too onerous but I think I'd need to understand first what the design decisions were for the deleted iterators and assignment operators.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions