#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 #include #include #include #include #include #include #include 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 indices 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 slice( Integer1 start, Integer2 stop) : base( object(start).ptr(), object(stop).ptr(), 0 ) {} template 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 // indices 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 struct range { RandomAccessIterator start; RandomAccessIterator stop; typename iterator_difference::type step; }; template slice::range get_indices( 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 ret; typedef typename iterator_difference::type difference_type; difference_type max_dist = std::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( 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( 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(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::type final_dist = std::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; } // Incorrect spelling. DO NOT USE. Only here for backward compatibility. // Corrected 2011-06-14. template slice::range get_indicies( const RandomAccessIterator& begin, const RandomAccessIterator& end) const { return get_indices(begin, end); } 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 : pytype_object_manager_traits<&PySlice_Type, slice> { }; } // !namesapce converter } } // !namespace ::boost::python #endif // !defined BOOST_PYTHON_SLICE_JDB20040105_HPP