views:

373

answers:

4

I've been a Java programmer almost exclusively for the past 8 years or so, and recently I've been playing with C++ again. Here's an issue that I've come up against with regards to iterators in C++ STL and Java.

In Java, you can write a method that takes an iterator like this:

void someMethod(Iterator<String> data) {
    // ...
}

You pass in an Iterator and the method does not need to know what the underlying collection of that iterator is, which is good.

In C++, there is no common base class for iterators (as far as I know). I'd have to write a function like this:

void some_function(std::vector<std::string>::const_iterator data) {
    // ...
}

In other words, some_function knows that the iterator is an iterator over a vector. That's not good, because I want the function to work regardless of what the underlying collection of the iterator is.

How can I do this in C++? If it isn't really possible, then what is the best way to create a function in C++ that takes a collection as a parameter, but that doesn't need to know what the exact kind of collection is?

Addendum

Thanks for the answers. In addition to the answers I found some good information on this in paragraph 7.5 (Iterator Traits) of the book The C++ Standard Library: A Tutorial and Reference (by Nicolai M. Josuttis). Paragraph 7.5.1 explains how to write specialized versions of functions for different iterator categories.

+5  A: 

You probably want to consider a function template. Look at how some of std <algorithm> function templates work such as std::for_each.

e.g.

template< class Iterator >
void some_function( Iterator first, Iterator last )
{
    // ...
}

You can then call a function generated from this template with many kinds of iterable ranges.

e.g.

std::vector< double > my_doubles;
// ... populate doubles
some_function( my_doubles.begin(), my_doubles.end() );


std::set< Custom > my_custom_class_set;
// ... populate ...
some_function( my_custom_class_set.begin(), my_custom_class_set.end() );

int raw_array[50];
// ... populate ...
some_function( raw_array, raw_array + 50 );
Charles Bailey
The key being that, in C++, templates are instantiated at compile time, so you don't need a common base class for your iterators, as long as they all have the same "shape".
Roger Lipscombe
Looking at how the standard algorithms like for_each() do this is a good idea, thanks.
Jesper
A: 

You can use the header file and specify the minimum requirements that the iterator must support.

So in the above example, you might want to rewrite the function as such:

template<typename T>
void some_function(std::forward_iterator<T> data) {
   ...
}

for something that requires to be able to only move the iterator forward (++) through a collection.

Timo Geusch
There is no `forward_iterator` in the `std` namespace. There is a `forward_iterator_tag`, but this isn't an iterator, it's a piece of information that you can get from an iterator use a traits class.
Charles Bailey
That's more or less the Java example translated into C++ one-to-one; something like this is what I tried first, and it doesn't work.
Jesper
my guess is you're trying to say: template<typename T> void some_function(std::iterator<std::forward_iterator_tag,T> it) { }This is an incorrect approach since there is no guarantee that the iterator passed in is derived from the std::iterator.The best way atm to define iterators is to name them property-wise and hope the use understands. eg: InputIterator, ForwardIterator, RandomIterator etc...Hopefully in the future when concepts are introduced things will become more palatable.
Beh Tou Cheh
You don't have to rely on naming conventions, you can dispatch a function template call to (e.g) a class template member with an extra template parameter constructed from `iterator_traits`. You can then partially specialize based on the traits parameter to provide optimized algorithms for e.g. random access iterators. This relies on there being a valid `iterator_traits` specialization for choice of optimal algorithms but it means that your template will work for other iterators and pointers.
Charles Bailey
+6  A: 
Beh Tou Cheh
Thanks, that looks like a good solution.
Jesper
+1  A: 

This is an example of one of the big differences between C++ and Java. The only abstraction tool Java has is runtime polymorphism (interfaces and abstract classes). In C++ you're not limited to that. You can create aliases for types and let classes have other associated/nested types. It lets you get away without runtime polymorhism in many cases. The compile-time kind of genericity has the advantage of being quite fast (no virtual function calls, inlining possibilities). Also, it eases life-time management when you don't have a garbage collector. You can simply create the objects on the stack.

Here's an (untested) example:

template<typename Iter>
typename std::iterator_traits<Iter>::value_type
sum(Iter begin, Iter end) {
   typedef typename std::iterator_traits<Iter>::value_type vt;
   vt accum = vt();
   while (begin!=end) {
      accum += *begin;
      ++begin;
   }
   return accum;
}

Here, "Iter" ist just a name. It doesn't actually impose any constraints on the type. In case you want to instantiate this template with a type that is not an iterator (at least in a structural sense) you'll get a compile-time error (compile-time duck typing). So, part of your job is documenting what kind of type you are expecting. This is usually done by picking some descriptive names of template parameters (i.e. ForwardIterator) and comments.

I should also mention that multiple "sum" functions are going to be "instantiated" if you use this function template with varying kinds of iterators. If you don't want this kind of code duplication and/or really need runtime polymorphism you can apply a technique called "type erasure". Type erasure for iterators is not part of the standard library, though. Also, I never felt the need to apply this technique for iterators. But you'll find the use of type erasure in other libraries like boost::any and boost::function.

There are a couple of other template tricks you can use to differentiate between different iterator categories (see "tag dispatching") or constrain your function template (see "SFINAE"). If you're interested in type erasure try googling for c++, type erasure, iterator. You basically create a handle class that manages a polymorphic object (through a pointer). This polymorphic object wraps some other object whose type you want to "erase" (hide).

sellibitze
"Compile-time duck typing" - Yes, it seems there's no way you can indicate that value_type has to be a specific type, or to specify other constraints on the types. Is this what C++ 'concepts' are supposed to add in a future version of C++?
Jesper
Yes. Concepts is sort of a type system for types. Unfortunately they didn't make it into C++0x. But some things you can do with native concepts support can be emulated with "template tricks" -- including restricting your function template to a certain kind of iterator type.
sellibitze