1

In C#, you can define a custom enumeration very trivially, eg:

public IEnumerable<Foo> GetNestedFoos()
{
    foreach (var child in _SomeCollection)
    {
        foreach (var foo in child.FooCollection)
        {
            yield return foo;
        }
        foreach (var bar in child.BarCollection)
        {
            foreach (var foo in bar.MoreFoos)
            {
                yield return foo;
            }
        }
    }
    foreach (var baz in _SomeOtherCollection)
    {
        foreach (var foo in baz.GetNestedFoos())
        {
            yield return foo;
        }
    }
}

(This can be simplified using LINQ and better encapsulation but that's not the point of the question.)


In C++11, you can do similar enumerations but AFAIK it requires a visitor pattern instead:

template<typename Action>
void VisitAllFoos(const Action& action)
{
    for (auto& child : m_SomeCollection)
    {
        for (auto& foo : child.FooCollection)
        {
            action(foo);
        }
        for (auto& bar : child.BarCollection)
        {
            for (auto& foo : bar.MoreFoos)
            {
                action(foo);
            }
        }
    }
    for (auto& baz : m_SomeOtherCollection)
    {
        baz.VisitAllFoos(action);
    }
}

Is there a way to do something more like the first, where the function returns a range that can be iterated externally rather than calling a visitor internally?

(And I don't mean by constructing a std::vector<Foo> and returning it -- it should be an in-place enumeration.)

I am aware of the Boost.Range library, which I suspect would be involved in the solution, but I'm not particularly familiar with it.

I'm also aware that it's possible to define custom iterators to do this sort of thing (which I also suspect might be involved in the answer) but I'm looking for something that's easy to write, ideally no more complicated than the examples shown here, and composable (like with _SomeOtherCollection).

I would prefer something that does not require the caller to use lambdas or other functors (since that just makes it a visitor again), although I don't mind using lambdas internally if needed (but would still prefer to avoid them there too).

Miral
  • 12,637
  • 4
  • 53
  • 93

4 Answers4

0

If I'm understanding your question correctly, you want to perform some action over all elements of a collection.

C++ has an extensive set of iterator operations, defined in the iterator header. Most collection structures, including the std::vector that you reference, have .begin and .end methods which take no arguments and return iterators to the beginning and the end of the structure. These iterators have some operations that can be performed on them manually, but their primary use comes in the form of the algorithm header, which defines several very useful iteration functions.

In your specific case, I believe you want the for_each function, which takes a range (as a beginning to end iterator) and a function to apply. So if you had a function (or function object) called action and you wanted to apply it to a vector called data, the following code would be correct (assuming all necessary headers are included appropriately):

std::for_each(data.begin(), data.end(), action);

Note that for_each is just one of many functions provided by the algorithm header. It also provides functions to search a collection, copy a set of data, sort a list, find a minimum/maximum, and much more, all generalized to work over any structure that has an iterator. And if even these aren't enough, you can write your own by reading up on the operations supported on iterators. Simply define a template function that takes iterators of varying types and document what kind of iterator you want.

template <typename BidirectionalIterator>
void function(BidirectionalIterator begin, BidirectionalIterator end) {
    // Do something
}

One final note is that all of the operations mentioned so far also operate correctly on arrays, provided you know the size. Instead of writing .begin and .end, you write + 0 and + n, where n is the size of the array. The trivial zero addition is often necessary in order to decay the type of the array into a pointer to make it a valid iterator, but array pointers are indeed random access iterators just like any other container iterator.

Silvio Mayolo
  • 62,821
  • 6
  • 74
  • 116
0

What you can do is writing your own adapter function and call it with different ranges of elements of the same type.

This is a non tested solution, that will probably needs some tweaking to make it compile,but it will give you an idea. It uses variadic templates to move from a collection to the next one.

template<typename Iterator, Args...>
visitAllFoos(std::pair<Iterator, Iterator> collection, Args&&... args)
{
  std::for_each(collection.first, collection.second, {}(){ // apply action });
  return visitAllFoos(std::forward<Args>(args)...);
}

//you can call it with a sequence of begin/end iterators
visitAllFoos(std::make_pair(c1.begin(), c1,end()), std::make_pair(c2.begin(), c2,end()))
dau_sama
  • 4,247
  • 2
  • 23
  • 30
0

I believe, what you're trying to do can be done with Boost.Range, in particular with join and any_range (the latter would be needed if you want to hide the types of the containers and remove joined_range from the interface).

However, the resulting solution would not be very practical both in complexity and performance - mostly because of the nested joined_ranges and type erasure overhead incurred by any_range. Personally, I would just construct std::vector<Foo*> or use visitation.

Andrey Semashev
  • 10,046
  • 1
  • 17
  • 27
  • Using visitation to construct a `std::vector` is how I'm doing it at the moment, but as I said I'm not especially happy with it. But thanks for confirming that. – Miral Jul 05 '15 at 23:11
0

You can do this with the help of boost::asio::coroutine; see examples at https://pubby8.wordpress.com/2014/03/16/multi-step-iterators-using-coroutines/ and http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/overview/core/coroutine.html.

beerboy
  • 1,304
  • 12
  • 12
  • Sorry for the late reply. It's a cute idea, but using a stackless coroutine for this sort of case would require unwrapping the range-based-for back into iterators (since local values are not preserved -- anything that needs to be remembered between yields has to be a member variable rather than a local variable), and that requires breaking up the code considerably (though not as badly as without a coroutine). Using a stackful coroutine would avoid that problem but that feels too heavy-handed for this sort of case. – Miral Mar 08 '16 at 23:03