19

I'm writing a C++ wrapper for a 3rd party C library.

The library provides some functions for iterating through a series of objects.

I want to write an iterator to wrap this behaviour so iteration is easier, but I cannot think how I will be able to provide the mandatory 'difference' type since the iterated objects don't have a meaningful relative order and the API I am working with does not provide a means of finding the number of objects available in advance.

I can't count the objects as they're iterated because although that would solve individual iterators it would render the difference between the end() iterator and other iterators undefined.

Alexis Wilke
  • 19,179
  • 10
  • 84
  • 156
Pharap
  • 3,826
  • 5
  • 37
  • 51
  • 5
    Just pick a big enough signed type (`std::ptrdiff_t` works). Just because you can't subtract iterators doesn't mean you can't define a type for it. See this example: http://en.cppreference.com/w/cpp/iterator/iterator – NathanOliver Oct 11 '17 at 18:42
  • 2
    Do you need to provide it? `std::iterator_traits` offers a default if you don't provide one – KABoissonneault Oct 11 '17 at 18:42
  • 2
    I think you can use void as the type. Any functions trying to calculate iterator distance will fail to compile. – Jonesinator Oct 11 '17 at 18:44
  • 1
    @KABoissonneault According to en.cppreference "[If Iterator does not have the five member types difference_type, value_type, pointer, reference, and iterator_category, then this template has no members by any of those names (std::iterator_traits is SFINAE-friendly)](http://en.cppreference.com/w/cpp/iterator/iterator_traits#Member_types)", which sounds like not providing a `differece_type` will prevent all the other definitions from being available through `std::iterator_traits`. – Pharap Oct 11 '17 at 18:46
  • 1
    You're right. I was mistaken – KABoissonneault Oct 11 '17 at 18:46
  • @KABoissonneault If it weren't for that little caveat then this wouldn't be as much of an issue. I'm hoping they'll reverse that decision in C++20. – Pharap Oct 11 '17 at 18:48

1 Answers1

19

All iterators in C++ need to provide some sort of difference_type. Whether or not that type is meaningful or useful is a completely separate issue.

From what you're describing, it seems like you're working with an input iterator, which gives you the ability to make a single pass over a stream of data but not to back up or save positions in the stream. For input iterators, there's still a requirement to have a difference_type defined, but there's no meaningful way to take the difference of two iterators. The only way to measure distance is to manually count up how many elements you've seen as you go.

Using the default of std::ptrdiff_t is probably very reasonable here unless you have a data stream that can produce so many elements that you can't fit the number of them into a std::ptrdiff_t. Clients can then use this to count up how many things they've seen, even though your iterator doesn't actually let people compute distances between entries.

templatetypedef
  • 362,284
  • 104
  • 897
  • 1,065
  • Indeed I have defined `iterator_category` as `std::input_iterator_tag`. I think it's possible that the data stream could produce more elements than can be counted as it's pulling them from non-volatile memory (i.e. flash memory), but it's highly unlikely so this might be a reasonable compromise. – Pharap Oct 11 '17 at 18:58
  • 1
    This sounds to me more like an unordered associative container (eg. a hash table), whose iterator is a forward iterator. (That means that iteration order is unspecified but consistent while the container is not modified.) The standard library provides `std::distance` which computes the distance by counting steps and returns a `distance_type`, so that type must be meaningful and is arguably useful (or at least has a defined use.). Having said all that, I agree that `ptrdiff_t` is appropriate. – rici Oct 11 '17 at 19:05
  • @rici It's not a hash table, but most of the implications (i.e. consistent but unspecified iteration order whilst unmodified) are the same. – Pharap Oct 11 '17 at 19:09
  • @pharap: that is what i meant by "more like". That is, it has more in common with a hash table iterator than with an input tokeniser (for example) and thus should be a forward iterator, not an input iterator. – rici Oct 11 '17 at 19:11
  • @rici Not quite. The problem with that is that a forward iterator has a 'multipass guarantee' and both the way I'm implementing the iterator and the underlying API break that guarantee so it can't be a forward iterator. – Pharap Oct 11 '17 at 21:11
  • @Pharap: OK, in that case it doesn't have consistent but unspecified iteration order and a forward iterator is not appropriate. (But I've got to say that containers without a consistent iteration order strike me as unusual.) – rici Oct 11 '17 at 21:17
  • @rici It's because the underlying API is passing the data out through an out param (i.e. a pointer to a struct) and that data is stored in the iterator, hence the multipass guarantee clause that: "If a and b compare equal (a == b is contextually convertible to true) then either they are both non-dereferenceable or *a and *b are references bound to the same object " would be violated, because `*a` and `*b` would be references to different objects with the same value instead of being references to the same object. – Pharap Oct 11 '17 at 21:25
  • It seems to me that we are just reproducing things we do not really understand... :-( – André Caldas Mar 29 '23 at 20:22