2

How does one write a function in C++ that takes in as a parameter an iterator of a certain type in a container-agnostic way? i.e.

// C# version
void foo(IEnumerable<MyConcreteClass> t)
{
   foreach(MyConcreteClass c in t)
   {
     c.MyFunction();
   }
}

Reading about iterators it seems I'm supposed to do something like this:

template<MyIter>
void foo(MyIter start, MyIter end, std::input_iterator_tag type)
{
  // How would the next part work?  Do I do:
  while (start != end)
  {
    MyConcreteClass* c = *start; // this will compile iff the parameter is correct.
    c->MyFunction();
    start++;
}

In the case of an invalid iterator type being passed in (e.g. a std::unordered_set<MyOtherClass*>::iterator), I feel that the compiler error generated using this method would just be some sort of invalid cast error at the line where I dereference start; I'd like instead to get an error that I'm actually passing in an iterator of the wrong type. Is there a better way to do this? I'd be nice to say "MyIter must be an iterator from a container of MyConcreteClass"

BTW, C++11 mechanisms are OK.

atanamir
  • 4,833
  • 3
  • 24
  • 20

2 Answers2

3

If you want, you can add something like

static_assert(std::is_convertible<decltype(*start), MyConcreteClass*>::value, "MyIter must be an iterator from a container of MyConcreteClass");

to anywhere inside the body of the function.

Be aware that this tightly couples MyConcreteClass and foo, so if you're sure you want that then it's fine. But if you don't, you can genericize it more and write

template<MyIter>
void foo(MyIter start, MyIter end)
{
  while (start != end) {
    auto c = *start;
    c->MyFunction();
    // Or instead of the above two statements:
    // (*c)->MyFunction(); 
    start++;
  }
}

This will work for a pointer to any type that has a MyFunction function, so now you could even use containers of unique_ptrs, whereas you couldn't before. But, you can't as easily write the assert statement for this one, so it's a tradeoff. The tradition of C++ is just to document the function (e.g. "This function works for all types that have a MyFunction() available behind an operator->") and let the compiler error if they violate the requirement.

uk4321
  • 1,028
  • 8
  • 18
  • +1. For the second solution, you could also develop a `static_assert`-based test (to get nicer error messages) using a compile-time test for the existence of a member function like this: http://stackoverflow.com/a/4354498/777186 – jogojapan Dec 01 '13 at 05:20
  • Just to clarify, I want the tight coupling -- a better example would be like `XmlDocument` and `XmlNode`. If, in C#, `XMLDocument` has a method like `AddChildren(IEnumerable nodes)`. Let's say I'm writing an `XmlDocument` class in C++ -- I would write that method like you suggest, with `static_assert`? – atanamir Dec 01 '13 at 05:31
  • 1
    @atanamir yes, if clients can hold their `XmlNode`s in their own containers, then you would write your original version with the added `static_assert`. – uk4321 Dec 01 '13 at 05:36
2

For your function, you may use something like:

template <template <typename...> class Container>
void foo(Container<MyConcreteClass>& container)
{
   for (auto elem : container)
   {
     elem.MyFunction();
   }
}

A more complete example fore extra parameter after: You may use similar to work for parameter before

template <template <typename...> class Container, typename... Ts>
void foo(Container<MyConcreteClass, Ts...>& container)
{
   for (auto elem : container)
   {
     elem.MyFunction();
   }
}

If you use both (before and after), it may be seen as ambiguous (my gcc fails to compile).

Jarod42
  • 203,559
  • 14
  • 181
  • 302
  • That looks really nice. Is that a variadic template? – atanamir Dec 02 '13 at 19:50
  • Would this still work if the container type isn't the first template argument of whatever container class is being passed in? e.g. `MyContainer` – atanamir Dec 02 '13 at 19:54
  • I use variadic template because some template class take extra parameter (allocator, comparer, ...). My solution need adaptation to work with extra parameter, (I try with parameter before and after, but my compilo crashes, as ideone :-/). – Jarod42 Dec 03 '13 at 11:40