50

Or is it safe to use vector if the Enumerator of T is just listing all the elements?

derekhh
  • 5,272
  • 11
  • 40
  • 60
  • There is an Equivalent you could use in C++ would you care to see a code example – MethodMan Jan 06 '12 at 21:22
  • @DJKRAZE: Thanks! I'm just trying to see if there are more appropriate approaches other than using vector, that might allow our own implementations of GetEnumerator() in C++ as well. – derekhh Jan 06 '12 at 21:23

4 Answers4

57

It isn't needed in C++, and here's why:

C# only supports dynamic polymorphism. So to create a reusable algorithm, you need an interface which all iterators will implement. That's IEnumerator<T>, and IEnumerable<T> is a factory for returning an iterator.

C++ templates, on the other hand, support duck typing. That means you don't need to constrain a generic type parameter by an interface in order to access members -- the compiler will look up members by name for each individual instantiation of the template.

C++ containers and iterators have implicit interfaces which is equivalent to .NET IEnumerable<T>, IEnumerator<T>, ICollection<T>, IList<T>, namely:

For containers:

  • iterator and const_iterator typedefs
  • begin() member function -- fills the need for IEnumerable<T>::GetEnumerator()
  • end() member function -- instead of IEnumerator<T>::MoveNext() return value

For forward iterators:

  • value_type typedef
  • operator++ -- instead of IEnumerator<T>::MoveNext()
  • operator* and operator-> -- instead of IEnumerator<T>::Current
  • reference return type from operator* -- instead of IList<T> indexer setter
  • operator== and operator!= -- no true equivalent in .NET, but with container's end() matches IEnumerator<T>::MoveNext() return value

For random access iterators:

  • operator+, operator-, operator[] -- instead of IList<T>

If you define these, then standard algorithms will work with your container and iterator. No interface is needed, no virtual functions are needed. Not using virtual functions makes C++ generic code faster than equivalent .NET code, sometimes much faster.


Note: when writing generic algorithms, it's best to use std::begin(container) and std::end(container) instead of the container member functions. That allows your algorithm to be used with raw arrays (which don't have member functions) in addition to the STL containers. Raw arrays and raw pointers satisfy all other requirements of containers and iterators, with this single exception.

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • 7
    This is a good explanation for consuming IEnumerable equivalents but what about producing them? What if I want to define an interface that exposes a member I can do begin() and end() over but without caring about the specific type that implements that member? – Sander Aug 09 '13 at 08:14
  • 4
    Andrei Alexandrescu disagrees with you. See "Iterators must go": http://zao.se/~zao/boostcon/09/2009_presentations/wed/iterators-must-go.pdf C++/D Ranges are the suggested replacement, and wouldn't you know, ranges match almost exactly the .NET IEnumerator interface. – naasking Nov 15 '13 at 17:00
  • 2
    @naasking: I don't know how that constitutes "disagreement". Now we have two ways to iterate ranges without a virtually dispatched interface. Your claim that ranges are `IEnumerator` shows ignorance of what `IEnumerator` actually is. Ranges are duck-types, .NET `IEnumerable` is dynamically dispatched. And please note that ranges, for all their advantages, are still not the canonical way of doing things in Standard C++. – Ben Voigt Nov 15 '13 at 17:09
  • Duck typing and dynamic dispatch are not mutually exclusive. C# 4.0 performs duck typing via dynamic dispatch via a polymorphic inline cache. That C++ ranges are not dynamically dispatched is irrelevant. The semantic correspondence between IEnumerator and ranges is 1:1, modulo the pointer semantics of C++. – naasking Nov 15 '13 at 18:49
  • 1
    @naasking: C# `IEnumerator` is not duck typed. C++ iterators and ranges have no virtual functions. Duck typing is 100% relevant, because it is the reason that C++ iterators and ranges do not need to inherit from a common base class. Oh, the C# `dynamic` keyword is not the same as dynamic dispatch, as your comment suggests. It is *dynamic binding*. Which is, btw, even slower than dynamic dispatch, even with the DLR cache. – Ben Voigt Nov 15 '13 at 20:15
  • Inheritance is irrelevant, only semantics matter. The domain and range of IEnumerator and C++ ranges match 1:1. That's a strong isomorphism. Dynamism is a red herring. Also, your terminology is not standard. Dispatch is dynamic binding of methods at runtime via a vtable. Finally, duck typing is also implementable via dynamic binding, so you contradict yourself saying that C++ iterators aren't the same as IEnumerator due to dispatch. What you call "duck typing" is specifically syntactic abstraction, which is irrelevant to semantics. Duck typing is a semantic property, not syntactic. – naasking Nov 15 '13 at 20:39
  • 1
    @naasking: You show your C++ illiteracy, when you say that dynamism is a red herring. Dynamism has a runtime cost, moderate for virtual dispatch (like `IEnumerable`), and very high for dynamic binding (which is the only form of "duck typing" that C# has). The language design of C++ has a strong emphasis on avoiding runtime costs. Dynamism *is* semantics. Please stop abusing the term *dynamic dispatch* -- it is synonymous with *virtual dispatch*, not *dynamic binding*. Duck typing in C# needs *dynamic binding*. And finally, the question is about `IEnumerable`, which is not duck typed. – Ben Voigt Dec 14 '14 at 18:55
  • 1
    This is a good start -- but can you give a simple example of how to build an iterator in C++? -- In C# it's really simple to just use `yield return` (you never actually need to implement IEnumerable manually unless you're doing something wonky). How would you do the equivalent in C++? – BrainSlugs83 May 31 '16 at 02:47
  • 1
    @BrainSlugs83: There are plenty of examples of native C++ iterators around. The simplest one is just a pointer. There's no equivalent to `yield return`... but that really is a coroutine implementation and has nothing much to do with container access. The C++ standards committee is looking at several different coroutine implementations; some even have experimental implementations in various compilers. But there's nothing standardized along those lines. – Ben Voigt May 31 '16 at 02:51
  • Also, you can build arbitrary generators and give them the same interface as iterators. Have a look at this example: http://stackoverflow.com/a/1388949/103167 – Ben Voigt May 31 '16 at 02:53
  • I think some people here confused IEnumerable/IEnumerator with foreach. IEnumerable is not duck-typed. foreach is. Having both (templated iterators and run-time iterators) is better than either one or the other. C++ STL is simply terrible in this case. Try to make a template in a virtual function implementation and you will see the issue. – Paulo Zemek Feb 02 '18 at 06:36
  • 1
    @PauloZemek: Yet foreach is an extremely restricted form of duck typing, insufficient for a great many interesting algorithms. For example, you can't build duck-typed binary search in C#, as that requires random-access iterators, and foreach only does single-pass forward iteration. Just because you cannot virtually dispatch to a template does not mean templates aren't useful... When you care about performance, virtual dispatch has to be avoided anyway, and when you need polymorphism, virtually dispatched iterators are easily made in C++. – Ben Voigt Feb 02 '18 at 07:16
  • But since you challenged me, here is the "template in a virtual function implementation" -- namely an iterator which dereferences to a smart pointer. You can have `whatever_container>` and efficiently walk the container, while virtually dispatching on its elements too. – Ben Voigt Feb 02 '18 at 07:18
12

If we stick to the question strictly the answer is no, as far as I know. People have kept replying what is the substitute available in C++, which may be good info but not answers, and which the OP most probably knew already.

I completely disagree that "it is not needed," it is just that the designs of the C++ and .NET standard libraries are different. The main feature of IEnumerable<> is that it's polymorphic, and so it enables the caller to use whatever class he wants (array, List, Set etc.), while still providing compile-time strong typing, fail-safe even in library APIs.

The only alternative in C++ is templates. But C++ templates are not safely typed run-time generics, they are basically kind of macros. So first of all with templates in C++ you are forced to provide the whole template source code to whoever needs to use your template. Moreover if you make your library API templated you lose the ability to guarantee that a call to it will compile, and the code is not automatically self-documenting.

I fully sympathize with any other programmer who uses both C# and C++ and is frustrated with this point.

However C++2X is planned to add features including ranges (which may satisfy the OP?); as well as concepts (which address the weak/bad type-checking of templates -- flaw admitted by Bjarne Stroustrup himself), and modules (which may or may not help reducing the pain from header-only templates).

J.P.
  • 350
  • 2
  • 11
  • 1
    "C++ templates are not safely typed run-time generics" is incredibly misleading. They are safely typed (much more safely than .NET polymorphism) compile-time (which allows optimization) generics. – Ben Voigt Nov 02 '16 at 20:25
  • 4
    Let me rephrase it: C++ templates are but macros. C++ is of course strongly type-safe -- same as C#. – J.P. Feb 15 '17 at 11:45
  • 1
    C++ templates are substantially different in behavior from macros, both the weak C variety of macros and also any super-powerful language-independent macro processor you care to adopt. Templates and the type system are deeply intertwined. – Ben Voigt Feb 15 '17 at 18:32
  • Reading Alexandrescu's _The D Programming Language_ I see this is a longstanding debate... I understand why a language like C# opted for homogeneous/polymorphic generic but C++ is happy with heterogeneous templates and allowing specialized instantiation (even if it doesn't play along with separating header and implementation files which is another story...) "Homogeneous translation favors uniformity, simplicity, and compact generated code. [...] In contrast, heterogeneous translation favors specialization, expressive power, and speed of generated code." – J.P. Feb 20 '17 at 18:09
  • 1
    Completely agree with this: "Moreover if you make your library api templated you lose the ability to guarantee that a call to it will compile". – Paulo Zemek Feb 02 '18 at 06:23
10

The standard C++ way is to pass two iterators:

template<typename ForwardIterator>
void some_function(ForwardIterator begin, ForwardIterator end)
{
    for (; begin != end; ++begin)
    {
        do_something_with(*begin);
    }
}

Example client code:

std::vector<int> vec = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(vec.begin(), vec.end());

std::list<int> lst = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(lst.begin(), lst.end());

int arr[] = {2, 3, 5, 7, 11, 13, 17, 19};
some_function(arr + 0, arr + 8);

Yay generic programming!

fredoverflow
  • 256,549
  • 94
  • 388
  • 662
  • more generic way is to use free functions `std::begin` and `std::end` – Abyx Jan 06 '12 at 21:47
  • 1
    @Abyx: The client code is operating on non-dependent types that demonstrably have `.begin` and `.end` so it makes no odds here. In any case, the genericity of `some_function` is what's important and that choice in the client code doesn't affect its implementation. – CB Bailey Jan 06 '12 at 21:50
  • 1
    This is the opposite of what's being asked for, IMHO. This shows how to *consume* an iterator generically. -- How would you implement the actual iterator? (Using `IEnumerable` in C#, this is incredibly easy, you just use `yield return` -- but how do you do the equivalent in C++?) – BrainSlugs83 May 31 '16 at 02:50
6

IEnumerable<T> is conceptually very different from vector.

The IEnumerable provides forward-only, read-only access to a sequence of objects, regardless of what container (if any) holds the objects. A vector is actually a container itself.

In C++, should you want to provide access to a container without giving the details of this container, the convention is to pass in two iterators representing the beginning and end of the container.

A good example is the C++ STL definition of accumulate, which can be contrasted with IEnumerable<T>.Aggregate

In C++

   int GetProduct(const vector<int>& v)
   {
         // We don't provide the container, but two iterators
         return std::accumulate(v.begin(), v.end(), 1, multiplies<int>());
   }

In C#

  int GetProduct(IEnumerable<int> v)
  {
        v.Aggregate(1, (l, r) => l*r);
  }
Andrew Shepherd
  • 44,254
  • 30
  • 139
  • 205
  • 1
    This answer only shows the consuming side, and isn't terribly helpful. – BrainSlugs83 May 31 '16 at 02:52
  • 2
    @BrainSlugs83: Your comments and downvotes aren't terribly helpful. Nothing in the question asks about syntactic sugar for iterator implementation using coroutines, which is exactly what C# `yield return` is. This question doesn't emphasize either the implementation side or the consumption side, it asks about the iterator interface itself, and not convenience wrappers. You're imposing your own curiosity about implementation, which is natural, but not helpful. If you want to know about implementation details, ask your own question, don't hijack this one. – Ben Voigt May 31 '16 at 17:03