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:
- Define the standard iterator typedefs (
value_type,reference,pointer,difference_type,iterator_category), or - Have a specialization of
std::iterator_traitsdefined 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:
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: