#ifndef BOOST_PYTHON_SLICE_JDB20040105_HPP | |
#define BOOST_PYTHON_SLICE_JDB20040105_HPP | |
// Copyright (c) 2004 Jonathan Brandmeyer | |
// Use, modification and distribution are subject to the | |
// Boost Software License, Version 1.0. (See accompanying file | |
// LICENSE_1_0.txt or copy at http://www.boost.org/LICENSE_1_0.txt) | |
#include <boost/python/detail/prefix.hpp> | |
#include <boost/config.hpp> | |
#include <boost/python/object.hpp> | |
#include <boost/python/extract.hpp> | |
#include <boost/python/converter/pytype_object_mgr_traits.hpp> | |
#include <boost/iterator/iterator_traits.hpp> | |
#include <iterator> | |
#include <algorithm> | |
namespace boost { namespace python { | |
namespace detail | |
{ | |
class BOOST_PYTHON_DECL slice_base : public object | |
{ | |
public: | |
// Get the Python objects associated with the slice. In principle, these | |
// may be any arbitrary Python type, but in practice they are usually | |
// integers. If one or more parameter is ommited in the Python expression | |
// that created this slice, than that parameter is None here, and compares | |
// equal to a default-constructed boost::python::object. | |
// If a user-defined type wishes to support slicing, then support for the | |
// special meaning associated with negative indicies is up to the user. | |
object start() const; | |
object stop() const; | |
object step() const; | |
protected: | |
explicit slice_base(PyObject*, PyObject*, PyObject*); | |
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice_base, object) | |
}; | |
} | |
class slice : public detail::slice_base | |
{ | |
typedef detail::slice_base base; | |
public: | |
// Equivalent to slice(::) | |
slice() : base(0,0,0) {} | |
// Each argument must be slice_nil, or implicitly convertable to object. | |
// They should normally be integers. | |
template<typename Integer1, typename Integer2> | |
slice( Integer1 start, Integer2 stop) | |
: base( object(start).ptr(), object(stop).ptr(), 0 ) | |
{} | |
template<typename Integer1, typename Integer2, typename Integer3> | |
slice( Integer1 start, Integer2 stop, Integer3 stride) | |
: base( object(start).ptr(), object(stop).ptr(), object(stride).ptr() ) | |
{} | |
// The following algorithm is intended to automate the process of | |
// determining a slice range when you want to fully support negative | |
// indicies and non-singular step sizes. Its functionallity is simmilar to | |
// PySlice_GetIndicesEx() in the Python/C API, but tailored for C++ users. | |
// This template returns a slice::range struct that, when used in the | |
// following iterative loop, will traverse a slice of the function's | |
// arguments. | |
// while (start != end) { | |
// do_foo(...); | |
// std::advance( start, step); | |
// } | |
// do_foo(...); // repeat exactly once more. | |
// Arguments: a [begin, end) pair of STL-conforming random-access iterators. | |
// Return: slice::range, where start and stop define a _closed_ interval | |
// that covers at most [begin, end-1] of the provided arguments, and a step | |
// that is non-zero. | |
// Throws: error_already_set() if any of the indices are neither None nor | |
// integers, or the slice has a step value of zero. | |
// std::invalid_argument if the resulting range would be empty. Normally, | |
// you should catch this exception and return an empty sequence of the | |
// appropriate type. | |
// Performance: constant time for random-access iterators. | |
// Rationale: | |
// closed-interval: If an open interval were used, then for a non-singular | |
// value for step, the required state for the end iterator could be | |
// beyond the one-past-the-end postion of the specified range. While | |
// probably harmless, the behavior of STL-conforming iterators is | |
// undefined in this case. | |
// exceptions on zero-length range: It is impossible to define a closed | |
// interval over an empty range, so some other form of error checking | |
// would have to be used by the user to prevent undefined behavior. In | |
// the case where the user fails to catch the exception, it will simply | |
// be translated to Python by the default exception handling mechanisms. | |
template<typename RandomAccessIterator> | |
struct range | |
{ | |
RandomAccessIterator start; | |
RandomAccessIterator stop; | |
typename iterator_difference<RandomAccessIterator>::type step; | |
}; | |
template<typename RandomAccessIterator> | |
slice::range<RandomAccessIterator> | |
get_indicies( const RandomAccessIterator& begin, | |
const RandomAccessIterator& end) const | |
{ | |
// This is based loosely on PySlice_GetIndicesEx(), but it has been | |
// carefully crafted to ensure that these iterators never fall out of | |
// the range of the container. | |
slice::range<RandomAccessIterator> ret; | |
typedef typename iterator_difference<RandomAccessIterator>::type difference_type; | |
difference_type max_dist = boost::detail::distance(begin, end); | |
object slice_start = this->start(); | |
object slice_stop = this->stop(); | |
object slice_step = this->step(); | |
// Extract the step. | |
if (slice_step == object()) { | |
ret.step = 1; | |
} | |
else { | |
ret.step = extract<long>( slice_step); | |
if (ret.step == 0) { | |
PyErr_SetString( PyExc_IndexError, "step size cannot be zero."); | |
throw_error_already_set(); | |
} | |
} | |
// Setup the start iterator. | |
if (slice_start == object()) { | |
if (ret.step < 0) { | |
ret.start = end; | |
--ret.start; | |
} | |
else | |
ret.start = begin; | |
} | |
else { | |
difference_type i = extract<long>( slice_start); | |
if (i >= max_dist && ret.step > 0) | |
throw std::invalid_argument( "Zero-length slice"); | |
if (i >= 0) { | |
ret.start = begin; | |
BOOST_USING_STD_MIN(); | |
std::advance( ret.start, min BOOST_PREVENT_MACRO_SUBSTITUTION(i, max_dist-1)); | |
} | |
else { | |
if (i < -max_dist && ret.step < 0) | |
throw std::invalid_argument( "Zero-length slice"); | |
ret.start = end; | |
// Advance start (towards begin) not farther than begin. | |
std::advance( ret.start, (-i < max_dist) ? i : -max_dist ); | |
} | |
} | |
// Set up the stop iterator. This one is a little trickier since slices | |
// define a [) range, and we are returning a [] range. | |
if (slice_stop == object()) { | |
if (ret.step < 0) { | |
ret.stop = begin; | |
} | |
else { | |
ret.stop = end; | |
std::advance( ret.stop, -1); | |
} | |
} | |
else { | |
difference_type i = extract<long>(slice_stop); | |
// First, branch on which direction we are going with this. | |
if (ret.step < 0) { | |
if (i+1 >= max_dist || i == -1) | |
throw std::invalid_argument( "Zero-length slice"); | |
if (i >= 0) { | |
ret.stop = begin; | |
std::advance( ret.stop, i+1); | |
} | |
else { // i is negative, but more negative than -1. | |
ret.stop = end; | |
std::advance( ret.stop, (-i < max_dist) ? i : -max_dist); | |
} | |
} | |
else { // stepping forward | |
if (i == 0 || -i >= max_dist) | |
throw std::invalid_argument( "Zero-length slice"); | |
if (i > 0) { | |
ret.stop = begin; | |
std::advance( ret.stop, (std::min)( i-1, max_dist-1)); | |
} | |
else { // i is negative, but not more negative than -max_dist | |
ret.stop = end; | |
std::advance( ret.stop, i-1); | |
} | |
} | |
} | |
// Now the fun part, handling the possibilites surrounding step. | |
// At this point, step has been initialized, ret.stop, and ret.step | |
// represent the widest possible range that could be traveled | |
// (inclusive), and final_dist is the maximum distance covered by the | |
// slice. | |
typename iterator_difference<RandomAccessIterator>::type final_dist = | |
boost::detail::distance( ret.start, ret.stop); | |
// First case, if both ret.start and ret.stop are equal, then step | |
// is irrelevant and we can return here. | |
if (final_dist == 0) | |
return ret; | |
// Second, if there is a sign mismatch, than the resulting range and | |
// step size conflict: std::advance( ret.start, ret.step) goes away from | |
// ret.stop. | |
if ((final_dist > 0) != (ret.step > 0)) | |
throw std::invalid_argument( "Zero-length slice."); | |
// Finally, if the last step puts us past the end, we move ret.stop | |
// towards ret.start in the amount of the remainder. | |
// I don't remember all of the oolies surrounding negative modulii, | |
// so I am handling each of these cases separately. | |
if (final_dist < 0) { | |
difference_type remainder = -final_dist % -ret.step; | |
std::advance( ret.stop, remainder); | |
} | |
else { | |
difference_type remainder = final_dist % ret.step; | |
std::advance( ret.stop, -remainder); | |
} | |
return ret; | |
} | |
public: | |
// This declaration, in conjunction with the specialization of | |
// object_manager_traits<> below, allows C++ functions accepting slice | |
// arguments to be called from from Python. These constructors should never | |
// be used in client code. | |
BOOST_PYTHON_FORWARD_OBJECT_CONSTRUCTORS(slice, detail::slice_base) | |
}; | |
namespace converter { | |
template<> | |
struct object_manager_traits<slice> | |
: pytype_object_manager_traits<&PySlice_Type, slice> | |
{ | |
}; | |
} // !namesapce converter | |
} } // !namespace ::boost::python | |
#endif // !defined BOOST_PYTHON_SLICE_JDB20040105_HPP |