简体   繁体   中英

C++ Iterator for std::set or std::vector

Let's say I have this:

struct HoldStuff {
  std::vector<StuffItem> items;
  std::set<StuffItem, StuffItemComparator> sorted_items;
}

Now, during a refactor, I may have stuff in items or I may have it in sorted_items , but regardless I want to do the same thing with each item. I want to do something like this:

HoldStuff holder;  // assume it was filled earlier
auto iter = holder.items.empty() ? holder.sorted_items.begin() :
                                   holder.items.begin();
auto iter_end = holder.items.empty() ? holder.sorted_items.end() :
                                       holder.items.end();
for (; iter != iter_end; ++iter) {
    auto& item = *iter;
    // Do stuff
}

When I go to compile this, I get errors complaining about incompatible operand types. Surely this is possible, no?

You have two options:

  • use type-erasure to get a runtime polymorphism on the iterator ( any_range or any_iterator )
  • delegate do_stuff to a function template that takes any kind of iterator

Here is an illustration with code:

#include <vector>
#include <set>
#include <iostream>

#include <boost/range/any_range.hpp>

template<typename Iterator>
void do_stuff(Iterator begin, Iterator end) {}

int main()
{
  std::vector<int> items;
  std::set<int> sorted_items;
  // first option
  typedef boost::any_range<int, boost::forward_traversal_tag, int&, std::ptrdiff_t> my_any_range;
  my_any_range r;
  if(items.empty())
    r = my_any_range(sorted_items);
  else
    r = my_any_range(items);
  for (auto& x : r) {
    std::cout << x << " ";
  }

  // second option
  // this could also be a lambda and std::for_each
  if(items.empty())
    do_stuff(sorted_items.begin(), sorted_items.end());
  else
    do_stuff(items.begin(), items.end());
  return 0;
}

The errors are correct: auto keyword works during compilation. In an easy way, it just deduces the type of assignment and uses this real type. But decision if it's vector's iterator or set's is made in runtime. So type can not be deduced. As SergeyA said, I'm wrong here, compiler fail on ?: operator, before auto. But the reason is still the same - it has no idea which type to use for the result.

You should probably use some more generic iterator type + polymorphism, or you can make this function parameterized on type , where T is an iterator type. I would prefer to do it this way:

template<class T> do_stuff(T &c) {  for (auto &el : c) { /*Whatever*/ } }

...

if (!items.empty()) {
    do_stuff(items);
} else if (!sorted_items.empty()) {
    do_stuff(sorted_items);
}     

PS: It's a conception, I didn't test the code.

Both sides of the ternary operator need to have the same type. In your case, they are different - std::vector<>::iterator and std::set<> iterator. A suitable solution seems to be some sort of a an iterator wrapper, which returns one or another depending on the initial condition.

auto means the compiler will deduce the type of what follows, at compilation time.

The return type of the ternary conditional operator in this case is what follows the question mark, so it is std::set<StuffItem, StuffItemComparator>::iterator and the compiler tries to cast what follows the column ( std::vector<StuffItem>::iterator ) to this incompatible type, hence the compiler error.

What you can do is make your item processing code generic, like so:

auto doStuff = [] (StuffItem& item) {
  // do stuff with item...
};
if( holder.items.size() )
  for_each( holder.items.begin(), holder.items.end(), doStuff );
else if( holder.sorted_items.size() )
  for_each( holder.sorted_items.begin(), holder.sorted_items.end(), doStuff );

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM