Question

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.

Was it helpful?

Solution 3

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

OTHER TIPS

You could use templates. For instance:

#include <iostream>

template<typename C>
void foo(C const& c)
{
    std::cout << "{ ";
    for (auto const& x : c)
    {
        std::cout << x << " ";
    }
    std::cout << "}";
}

And here is how you would use it:

#include <set>
#include <vector>

int main()
{
    std::vector<int> v = {1, 2, 3};
    foo(v);

    std::cout << std::endl;

    std::set<std::string> s = {"Hello,", "Generic", "World!"};
    foo(s);
}

Live example.

This is exactly what iterators were designed for.

template <class It>
void print_some_strings(It first, It last) {
    while (first != last)
        std::cout << *first++ << '\n';
}

In C#, I would pass IEnumerable or something like that.

C++ uses the more pythonic approach of duck typing to define interfaces(generally called a concept in C++), rather than using inheritance. To do duck typing in C++, you use a template function like this:

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

In python, duck typing is done at runtime, but in C++ it is done at compile time, so there is no runtime cost to duck typing, and everything is checked at compile time as well.

Here is more info about C++, to help with looking for information. First, the equivalent of the IEnumerator<T> is the iterator in C++. Here is a page about the different iterator categories, and what needs to be implemented for iterators. For legacy reasons, iterators are modeled after pointers in C, which lets you use C arrays with the standard C++ algorithms.

However, unlike IEnumerator<T>, iterators must come in pairs. An iterator to the begining and the end(which is one past the last element). So, the equivalent of IEnumerable<T> in C++ is called a range. In C++11, a range is defined by two free functions, begin(T) and end(T)(it can also be implemented as member function .begin() and .end()).

By defining the concept(aka interface) as two free function, as opposed to using inheritance, ranges can be implemented non-intrusively. So for example, if you have work with some legacy api that uses C style linked lists. They can now be adapted as a C++11 range and used inside a C++ for loop.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top