Search code examples
c++c++11ienumerablestdvectorstdset

How do I iterate over collections generically in C++?


Simply put, if I have a set and vector how do I create a generic method that can handle both as params.

All I want to do, is iterate over either types of collections. Sounds like it should be trivial but I'm missing something.

void printMeSomeStrings(somebaseclass<string> strings) {
  for (auto& str : strings) {
    cout << str << endl;
  }
}

In C#, I would pass IEnumerable or something like that. Then I could iterate over the collection.

Any general reading explaining the answer would be appreciated.


Solution

  • The first option is to put the code doing the iterating in a template. This requires exposing the implementation to everyone who uses it, which has disadvantages.

    Basically, take a type C as a template parameter, then write your code in terms of that type C.

    template<typename C>
    void printMeSomeStrings(C&& strings) {
      for (auto const& str : strings) {
        cout << str << endl;
      }
    }
    

    If you want to be able to have a strong barrier between interface and implementation, the C++11 approach would be to engage in type erasure on a for-iterable container, and then expose a for-iterable container, like how std::function works.

    This is trickier. I personally find writing a for_each function easier than writing a full blown iteration adapter. If you want the full blown container iteration type erasure object, start with boost, or ask me below and I might do it.

    The for_each adaptor is easy, however.

    #include <functional>
    #include <utility>
    #include <iterator>
    #include <memory>
    
    template<typename T>
    struct for_each_helper_interface {
      virtual ~for_each_helper_interface() {}
      virtual void for_each( std::function< void(T) > const& ) = 0;
    };
    template<typename C, typename T>
    struct for_each_helper:for_each_helper_interface<T> {
      C& c;
      for_each_helper( C& in ):c(in) {}
      virtual void for_each( std::function< void(T) > const& f ) override final {
        for( auto&& x:c ) {
          f(x);
        }
      }
    };
    template<typename T>
    struct for_each_adaptor {
      std::unique_ptr<for_each_helper_interface<T>> pImpl;
      void for_each( std::function< void(T) > const& f ) {
        if (pImpl) {
          pImpl->for_each(f);
        }
      }
      template<typename C>
      for_each_adaptor( C&& c ): pImpl( new for_each_helper<C, T>( std::forward<C>(c) ) ) {}
    };
    

    which will type-erase the container of T (or a type convertible to T!) and expose a for_each method that lets you iterate over the contents of the container. Use like this:

    #include <set>
    #include <iostream>
    #include <vector>
    void print_stufF( for_each_adaptor<std::string const&> c ) {
      c.for_each([&](std::string const&s){
        std::cout << s << "\n";
      });
    }
    int main() {
       std::set<std::string> s;
       s.insert("hello");
       s.insert("world");
       print_stuff(s);
       std::vector<std::string> v;
       v.push_back("hola");
       v.push_back("bola");
       print_stuff(v);
     }
    

    What is going on here is that for each type used to construct our adaptor, we build a custom implementation of for each. We then store a pointer to the abstract base class of this custom class, and redirect for each calls to it.

    This means anything that specializes std::begin or defines its own begin need not be related: we create ad hoc relationships at point of use instead.

    Live example: http://ideone.com/xOqBkI