Skip to content

Iterators

C++ iterators are used to traverse through elements stored in a container. C++ iterators are external iterators that work in pairs, with a beginning iterator and an ending iterator. For example, std::vector has begin/end, cbegin/cend, rbegin/rend, etc.

Enumerable Support (Internal Iterators)

Rice makes it easy to add Enumerable support to C++ classes. The Enumerable module adds internal iterator support to a Ruby class as long as it defines an each instance method.

Rice makes this easy via the define_iterator method. define_iterator creates an each method and also mixes in the Enumerable module.

For example let's create a simple wrapper around std::vector (for full support please see std::vector).

#include <vector>
#include <rice/rice.hpp>

using namespace Rice;

extern "C"
void Init_IntVector()
{
  using IntVector = std::vector<int>;
  define_class<IntVector>("IntVector")
    .define_constructor(Constructor<IntVector>())
    .define_method<void(IntVector::*)(const IntVector::value_type&)>("push_back", &IntVector::push_back)
    .define_iterator<IntVector::iterator(IntVector::*)()>(&IntVector::begin, &IntVector::end);
}

Notice that we have to tell Rice which overloaded version of push_back, begin and end we want to expose For more information please see overloaded methods.

Once the iterator is defined you can write standard Ruby code such as:

intVector = IntVector.new
intVector.push_back(1)
intVector.push_back(2)
intVector.push_back(3)

result = intVector.map do |value|
  value * 2
end

Where the result will be [2, 4, 6].

Let's say you also want to expose std::vector's reverse iterator to Ruby using the method name reach. This is done by adding a third parameter to the define_iterator call, in this case it is set to "reach":

extern "C"
void Init_IntVector()
{
  define_class<IntVector>("IntVector")
    .define_iterator<IntVector::reverse_iterator(IntVector::*)()>(&IntVector::rbegin, &IntVector::rend, "reach");
}

Example Ruby code is then:

intVector = IntVector.new
intVector.push_back(1)
intVector.push_back(2)
intVector.push_back(3)

result = intVector.reach do |value|
  result << value * 2
end

Where the result will be [6, 4, 2].

Iterator Requirements

Rice uses std::iterator_traits to determine the value type, reference type, and other properties of iterators. This means your iterator must either:

  1. Define the standard iterator typedefs (value_type, reference, pointer, difference_type, iterator_category), or
  2. Have a specialization of std::iterator_traits defined for it

Most STL iterators and well-designed C++ iterators already satisfy these requirements. However, some libraries define iterators that lack these typedefs.

Missing Iterator Traits

If you encounter a compiler error like:

error C2039: 'value_type': is not a member of 'std::iterator_traits<MyIterator>'

You need to provide a std::iterator_traits specialization for that iterator. For example:

#include <iterator>

// Specialization for an iterator that lacks proper traits
namespace std
{
  template<>
  struct iterator_traits<MyNamespace::MyIterator>
  {
    using iterator_category = forward_iterator_tag;
    using value_type = MyValueType;
    using difference_type = ptrdiff_t;
    using pointer = MyValueType*;
    using reference = MyValueType&;
  };
}

Place this specialization before your Rice bindings code, typically right after the includes.

Common Iterator Categories

Choose the appropriate iterator_category based on your iterator's capabilities:

Category Operations Supported
input_iterator_tag Read-only, single-pass (++, *, ==)
forward_iterator_tag Read/write, multi-pass (++, *, ==)
bidirectional_iterator_tag Forward + backward (++, --, *, ==)
random_access_iterator_tag Bidirectional + random access (+, -, [], <)

Enumerator Support (External Iterators)

Ruby supports external iterators via the Enumerator class. The define_iterator method automatically adds support for Enumerators.

Enumerators can be created by calling an iterator method without a block, in the same way you can call Array#each or other methods without a block. For example:

intVector = IntVector.new
intVector.push_back(1)
intVector.push_back(2)
intVector.push_back(3)

# Get an enumerator
enumerator = intVector.each

# Now  use it
enumerator.map |i|
  i * 2
end