The solution I came across was to create an array_ref class that provides a non-owner view in a C-style array. Another array_indexing_suite class, copied from boost::python::vector_indexing_suite , with all member functions that mutate the size of the array, resized from the original to throw errors, was then used to carry array_ref to allow indexing operations with Python. Relevant code below:
array_ref
array_indexing_suite
#include <boost/python.hpp> #include <boost/python/suite/indexing/indexing_suite.hpp> #include <algorithm> #include <cstddef> #include <iterator> #include <type_traits> // Forward declaration template< typename Array, bool NoProxy, typename DerivedPolicies> class array_indexing_suite; namespace detail { template<typename Array, bool NoProxy> struct final_array_derived_policies : array_indexing_suite<Array, NoProxy, final_array_derived_policies<Array, NoProxy>> {}; } /* namespace detail */ template< typename Array, bool NoProxy = std::is_arithmetic<typename Array::value_type>::value, typename DerivedPolicies = detail::final_array_derived_policies<Array, NoProxy> > class array_indexing_suite : public boost::python::indexing_suite<Array, DerivedPolicies, NoProxy> { public: typedef typename Array::value_type data_type; typedef typename Array::value_type key_type; typedef typename Array::size_type index_type; typedef typename Array::size_type size_type; typedef typename Array::difference_type difference_type; static data_type& get_item( Array& arr, index_type i ) { return arr[i]; } static void set_item( Array& arr, index_type i, data_type const& v ) { arr[i] = v; } static void delete_item( Array& /*arr*/, index_type /*i*/ ) { ::PyErr_SetString( ::PyExc_TypeError, "Cannot delete array item" ); boost::python::throw_error_already_set(); } static size_type size( Array& arr ) { return arr.size(); } static bool contains( Array& arr, key_type const& key ) { return std::find( arr.cbegin(), arr.cend(), key ) != arr.cend(); } static index_type get_min_index( Array& ) { return 0; } static index_type get_max_index( Array& arr ) { return arr.size(); } static bool compare_index( Array&, index_type a, index_type b ) { return a < b; } static index_type convert_index( Array& arr, PyObject *i_ ) { boost::python::extract<long> i(i_); if( i.check() ) { long index = i(); if( index < 0 ) { index += static_cast<decltype(index)>(DerivedPolicies::size( arr )); } if( ( index >= long(arr.size()) ) || ( index < 0 ) ) { ::PyErr_SetString( ::PyExc_IndexError, "Index out of range" ); boost::python::throw_error_already_set(); } return index; } ::PyErr_SetString( ::PyExc_TypeError, "Invalid index type" ); boost::python::throw_error_already_set(); return index_type(); } static boost::python::object get_slice( Array& arr, index_type from, index_type to ) { if( from > to ) { return boost::python::object( Array() ); } return boost::python::object( Array( arr.begin() + from, arr.begin() + to ) ); } static void set_slice( Array& arr, index_type from, index_type to, data_type const& v ) { if( from > to ) { return; } else if( to > arr.size() ) { ::PyErr_SetString( ::PyExc_IndexError, "Index out of range" ); boost::python::throw_error_already_set(); } else { std::fill( arr.begin() + from, arr.begin() + to, v ); } } template<typename Iter> static void set_slice( Array& arr, index_type from, index_type to, Iter first, Iter last ) { auto num_items = std::distance( first, last ); if( ( from + num_items ) > arr.size() ) { ::PyErr_SetString( ::PyExc_IndexError, "Index out of range" ); boost::python::throw_error_already_set(); return; } if( from > to ) { std::copy( first, last, arr.begin() + from ); } else { if( static_cast<decltype(num_items)>( to - from ) != num_items ) { ::PyErr_SetString( ::PyExc_TypeError, "Array length is immutable" ); boost::python::throw_error_already_set(); return; } std::copy( first, last, arr.begin() + from ); } } static void delete_slice( Array& /*arr*/, index_type /*from*/, index_type /*to*/ ) { ::PyErr_SetString( ::PyExc_TypeError, "Cannot delete array item(s)" ); boost::python::throw_error_already_set(); } };
Finally, the bit needed to create the bindings.
struct foo { char data[100]; }; BOOST_PYTHON_MODULE( foo_module ) { using namespace boost::python; class_<array_ref<unsigned char>>( "uchar_array" ) .def( array_indexing_suite<array_ref<unsigned char>>() ) ; class_<foo>( "foo", "Foo description" ) .add_property( "data", static_cast<array_ref<unsigned char>(*)( foo * )>( []( foo *obj ) { return array_ref<unsigned char>( obj->data ); }), "Array of data bytes" ) ; }