...one of the most highly
regarded and expertly designed C++ library projects in the
world.
— Herb Sutter and Andrei
Alexandrescu, C++
Coding Standards
|
Boost.Python Headers <boost/python/indexing/indexing_suite.hpp>
|
Indexing is a Boost Python facility for easy exportation of indexable C++ containers to Python. Indexable containers are containers that allow random access through the operator[] (e.g. std::vector).
While Boost Python has all the facilities needed to expose indexable C++ containers such as the ubiquitous std::vector to Python, the procedure is not as straightforward as we'd like it to be. Python containers do not map easily to C++ containers. Emulating Python containers in C++ (see Python Reference Manual, Emulating container types) using Boost Python is non trivial. There are a lot of issues to consider before we can map a C++ container to Python. These involve implementing wrapper functions for the methods __len__, __getitem__, __setitem__, __delitem__, __iter__ and __contains.
The goals:
val = c[i] c[i].m() val == c[i]
The indexing_suite class is the base protocol class for the management of C++ containers intended to be integrated to Python. The objective is make a C++ container look and feel and behave exactly as we'd expect a Python container. The class automatically wraps these special Python methods (taken from the Python reference: Emulating container types):
>=
0. Also,
an object that doesn't define a __nonzero__() method and whose __len__() method returns zero is considered to be
false in a Boolean context.
self[key]
. For sequence types,
the accepted keys should be integers and slice
objects. Note that the special
interpretation of negative indexes (if the class wishes to
emulate a sequence type) is up to the __getitem__() method. If key is of
an inappropriate type, TypeError
may be raised; if of a value outside the set of indexes for
the sequence (after any special interpretation of negative
values), IndexError should be
raised. Note:
for loops expect that an IndexError will be raised for illegal
indexes to allow proper detection of the end of the
sequence.
self[key]
. Same note as for
__getitem__(). This should only be
implemented for mappings if the objects support changes to the
values for keys, or if new keys can be added, or for sequences if
elements can be replaced. The same exceptions should be raised
for improper key values as for the __getitem__() method.
self[key]
. Same note as for
__getitem__(). This should only be
implemented for mappings if the objects support removal of keys,
or for sequences if elements can be removed from the sequence.
The same exceptions should be raised for improper key
values as for the __getitem__() method.
Iterator objects also need to implement this method; they are required to return themselves. For more information on iterator objects, see ``Iterator Types'' in the Python Library Reference.
The indexing_suite is not meant to be used as is. A couple of policy functions must be supplied by subclasses of indexing_suite. However, a set of indexing_suite subclasses for the standard indexable STL containers will be provided, In most cases, we can simply use the available predefined suites. In some cases, we can refine the predefined suites to suit our needs.
The vector_indexing_suite class is a predefined indexing_suite derived class designed to wrap std::vector (and std::vector like [i.e. a class with std::vector interface]) classes (currently, this is the only predefined suite available). It provides all the policies required by the indexing_suite.
Example usage:
class X {...}; ... class_<std::vector<X> >("XVec") .def(vector_indexing_suite<std::vector<X> >()) ;
XVec is now a full-fledged Python container (see the example in full, along with its python test).
Template Parameter |
Requirements | Semantics | Default |
Container | A class type | The container type to be wrapped to Python. | |
DerivedPolicies | A subclass of indexing_suite | Derived classes provide the policy hooks. See DerivedPolicies below. | |
NoProxy | A boolean | By default indexed elements have Python reference semantics and are returned by proxy. This can be disabled by supplying true in the NoProxy template parameter. | false |
Element | The container's element type. | Container::value_type | |
Key | The container's key type. | Container::value_type | |
Index | The container's index type. | Container::size_type |
template <
class Container , class DerivedPolicies , bool NoProxy = false , class Element = typename Container::value_type , class Key = typename Container::value_type , class Index = typename Container::size_type >
class indexing_suite : unspecified { public: indexing_suite(); // default constructor }
static element_type& get_item(Container& container, index_type i); static object get_slice(Container& container, index_type from, index_type to); static void set_item(Container& container, index_type i, element_type const& v); static void set_slice( Container& container, index_type from, index_type to, element_type const& v ); template <class Iter> static void
set_slice(Container& container, index_type from, index_type to, Iter first, Iter last ); static void delete_item(Container& container, index_type i); static void delete_slice(Container& container, index_type from, index_type to); static size_t size(Container& container); template <class T> static bool contains(Container& container, T const& val); static index_type convert_index(Container& container, PyObject* i); static index_type adjust_index(index_type current, index_type from, index_type to, size_type len );
Most of these policies are self explanatory. However, convert_index and adjust_index deserve some explanation.
convert_index converts a Python index into a C++ index that the container can handle. For instance, negative indexes in Python, by convention, start counting from the right(e.g. C[-1] indexes the rightmost element in C). convert_index should handle the necessary conversion for the C++ container (e.g. convert -1 to C.size()-1). convert_index should also be able to convert the type of the index (A dynamic Python type) to the actual type that the C++ container expects.
When a container expands or contracts, held indexes to its elements must be adjusted to follow the movement of data. For instance, if we erase 3 elements, starting from index 0 from a 5 element vector, what used to be at index 4 will now be at index 1:
[a][b][c][d][e] ---> [d][e] ^ ^ 4 1adjust_index takes care of the adjustment. Given a current index, the function should return the adjusted index when data in the container at index from..to is replaced by len elements.
Template Parameter |
Requirements | Semantics | Default |
Container | A class type | The container type to be wrapped to Python. | |
NoProxy | A boolean | By default indexed elements have Python reference semantics and are returned by proxy. This can be disabled by supplying true in the NoProxy template parameter. | false |
DerivedPolicies | A subclass of indexing_suite | The vector_indexing_suite may still be derived to further tweak any of the predefined policies. Static polymorphism through CRTP (James Coplien. "Curiously Recurring Template Pattern". C++ Report, Feb. 1995) enables the base indexing_suite class to call policy function of the most derived class |
template <
class Container,
bool NoProxy = false,
class DerivedPolicies = unspecified_default
class vector_indexing_suite
: public indexing_suite<Container, DerivedPolicies, NoProxy>
{
public:
typedef typename Container::value_type element_type;
typedef typename Container::value_type key_type;
typedef typename Container::size_type index_type;
typedef typename Container::size_type size_type;
typedef typename Container::difference_type difference_type;
static element_type&
get_item(Container& container, index_type i); static object get_slice(Container& container, index_type from, index_type to); static void
set_item(Container& container, index_type i, element_type const& v); static void set_slice(Container& container, index_type from, index_type to, element_type const& v); template <class Iter>
static void
set_slice(Container& container, index_type from,
index_type to, Iter first, Iter last); static void delete_item(Container& container, index_type i); static void delete_slice(Container& container, index_type from, index_type to);
static size_t size(Container& container); static bool contains(Container& container, key_type const& key); static index_type convert_index(Container& container, PyObject* i); static index_type adjust_index(index_type current, index_type from, index_type to, size_type len); };