Library of Assembled Shared Sources
pyiteratorrange.h
Go to the documentation of this file.
1/** @file
2 * @author Bram de Greve (bram@cocamware.com)
3 * @author Tom De Muer (tom@cocamware.com)
4 *
5 * *** BEGIN LICENSE INFORMATION ***
6 *
7 * The contents of this file are subject to the Common Public Attribution License
8 * Version 1.0 (the "License"); you may not use this file except in compliance with
9 * the License. You may obtain a copy of the License at
10 * http://lass.sourceforge.net/cpal-license. The License is based on the
11 * Mozilla Public License Version 1.1 but Sections 14 and 15 have been added to cover
12 * use of software over a computer network and provide for limited attribution for
13 * the Original Developer. In addition, Exhibit A has been modified to be consistent
14 * with Exhibit B.
15 *
16 * Software distributed under the License is distributed on an "AS IS" basis, WITHOUT
17 * WARRANTY OF ANY KIND, either express or implied. See the License for the specific
18 * language governing rights and limitations under the License.
19 *
20 * The Original Code is LASS - Library of Assembled Shared Sources.
21 *
22 * The Initial Developer of the Original Code is Bram de Greve and Tom De Muer.
23 * The Original Developer is the Initial Developer.
24 *
25 * All portions of the code written by the Initial Developer are:
26 * Copyright (C) 2004-2025 the Initial Developer.
27 * All Rights Reserved.
28 *
29 * Contributor(s):
30 *
31 * Alternatively, the contents of this file may be used under the terms of the
32 * GNU General Public License Version 2 or later (the GPL), in which case the
33 * provisions of GPL are applicable instead of those above. If you wish to allow use
34 * of your version of this file only under the terms of the GPL and not to allow
35 * others to use your version of this file under the CPAL, indicate your decision by
36 * deleting the provisions above and replace them with the notice and other
37 * provisions required by the GPL License. If you do not delete the provisions above,
38 * a recipient may use your version of this file under either the CPAL or the GPL.
39 *
40 * *** END LICENSE INFORMATION ***
41 */
42
43
44#ifndef LASS_GUARDIAN_OF_INCLUSION_UTIL_PYITERATORRANGE_H
45#define LASS_GUARDIAN_OF_INCLUSION_UTIL_PYITERATORRANGE_H
46
47#include "python_common.h"
48#include "pyobject_plus.h"
49#include "_lass_module.h"
50#include "pyshadow_object.h"
51
52
53#if LASS_COMPILER_TYPE == LASS_COMPILER_TYPE_MSVC
54# pragma warning(push)
55# pragma warning(disable: 4244)
56#endif
57
58namespace lass
59{
60namespace python
61{
62
63
64/** @defgroup PythonIterators Python Iterators
65 * @brief Exposing iterators to Python
66 *
67 * Lass provides three sets of building blocks to add iterator support to your
68 * Python exports:
69 *
70 * 1. `PyIteratorRange`: the central C++ type that implements the Python Iterator
71 * protocol. It's used for all exported Python iterators from the bindings.
72 * It's Python type is `_lass.PyIteratorRange` and will be type-hinted as
73 * `Iterator[Any]`.
74 *
75 * `PyIteratorRange` uses the pimpl idiom to hold an object that implements the
76 * `impl::PyIteratorRangeImplBase` interface to perform the actual C++ iteration.
77 *
78 * There's two common implementations of this interface:
79 * - `impl::PyIteratorRangeImpl`: for iterating over a C++ iterator pair
80 * - `impl::PyIndexIteratorRangeImpl`: for iterating over a sequence by index.
81 *
82 * You can directly return a `PyIteratorRange` from your bindings, or you can
83 * use one of the convenience methods below.
84 *
85 * 2. Two groups of macros:
86 *
87 * - `PY_CLASS_ITERFUNC()`, `PY_CLASS_ITERFUNC_DOC()` and `PY_CLASS_ITERFUNC_EX()`
88 * - `PY_CLASS_FREE_ITERFUNC()`, `PY_CLASS_FREE_ITERFUNC_DOC()` and `PY_CLASS_ITERFUNC_EX()`
89 *
90 * These older macros help you to define the `__iter__` method for your class by
91 * directly returning a `PyIteratorRange` object.
92 *
93 * 3. Range views:
94 *
95 * - `ContainerRangeView` with `makeContainerRangeView()`
96 * - `MemberRangeView` with `makeMemberRangeView()` and `makeMemberRangeViewFactory()`
97 * - `FreeRangeView` with `makeFreeRangeView()` and `makeFreeRangeViewFactory()`
98 * - `IndexedRangeView` with `makeIndexedRangeView()` and `makeIndexedRangeViewFactory()`
99 * - `FreeRangeView` with `makeFreeRangeView()` and `makeFreeRangeViewFactory()`
100 *
101 * The benefit of using these is that they'll use the actual value-type in the
102 * type-hints instead of `Any`.
103 *
104 * @ingroup Python
105 */
106namespace impl
107{
108 /// begin of iterators
109
110 /** Interface to C++ iteration, used by PyIteratorRange
111 * @ingroup PythonIterators
112 */
113 class LASS_PYTHON_DLL PyIteratorRangeImplBase
114 {
115 public:
116 PyIteratorRangeImplBase() = default;
117 virtual ~PyIteratorRangeImplBase() = default;
118 virtual PyObject* iterNext() = 0;
119 };
120
121 /** Implementation of PyIteratorRangeImplBase that iterates over a C++ iterator pair.
122 * @ingroup PythonIterators
123 */
124 template<typename Iterator>
125 class PyIteratorRangeImpl : public PyIteratorRangeImplBase
126 {
127 public:
128 PyIteratorRangeImpl(Iterator first, Iterator last): first_(first), last_(last), current_(first) {}
129 ~PyIteratorRangeImpl() = default;
130 PyObject* iterNext() override
131 {
132 if (current_ != last_)
133 {
134 return pyBuildSimpleObject(*current_++);
135 }
136 // according to python specs this is allowed and is equivalent of setting the
137 // stopiteration exception
138 return nullptr;
139 }
140 private:
141 Iterator first_;
142 Iterator last_;
143 Iterator current_;
144 };
145
146 /** Implementation of PyIteratorRangeImplBase to iterate over a sequence by index.
147 * @ingroup PythonIterators
148 */
149 template<typename ValueType, typename SizeType>
150 class PyIndexIteratorRangeImpl : public PyIteratorRangeImplBase
151 {
152 public:
153 using AtFunc = std::function<ValueType(SizeType)>;
154 PyIndexIteratorRangeImpl(AtFunc atFunc, SizeType size) : atFunc_(atFunc), size_(size), index_(0) {}
155 ~PyIndexIteratorRangeImpl() = default;
156 PyObject* iterNext() override
157 {
158 if (index_ < size_)
159 {
160 return pyBuildSimpleObject(atFunc_(index_++));
161 }
162 return nullptr; // equivalent to StopIteration in Python
163 }
164 private:
165 AtFunc atFunc_;
166 SizeType size_;
167 SizeType index_;
168 };
169}
170
171
172/** Python iterator object
173 *
174 * This is the class that implements the Python iterator interface and that is exposed
175 * to Python. It can be created directly with two iterators, or with a custom
176 * implementation deriving from `PyIteratorRangeImplBase`.
177 *
178 * @ingroup PythonIterators
179 */
180class LASS_PYTHON_DLL PyIteratorRange : public lass::python::PyObjectPlus
181{
182 PY_HEADER(PyObjectPlus)
183public:
184 using TPimpl = std::unique_ptr<impl::PyIteratorRangeImplBase>;
185
186 /** Construct PyIteratorRange with custom implementation */
187 PyIteratorRange(TPimpl pimpl);
188
189 /** Construct PyIteratorRange with default implemenation iterating between two iterators */
190 template<typename Iterator> PyIteratorRange(Iterator first, Iterator last):
191 PyIteratorRange(std::make_unique<impl::PyIteratorRangeImpl<Iterator>>(first, last))
192 {
193 }
194
195 /** Owner object of the iterators
196 *
197 * This is the object that owns the iterators that PyIteratorRange is iterating over
198 * and that must stay alive while the PyIteratorRange is in use. If the owning object
199 * is destroyed while the PyIteratorRange is still in use, the iterators will become
200 * invalid and any further attempts to use the PyIteratorRange will result in undefined
201 * behavior.
202 *
203 * Setting the owner on the PyIteratorRange will prevent that it is destroyed while
204 * the PyIteratorRange is still in use.
205 *
206 * If a method or free method returns a PyIteratorRange, the owner will automatically
207 * be set to `self`, the instance the method was called on, if the owner was not yet
208 * set. If that doesn't work for your use case, you must set the owner manually.
209 */
210 ///@{
211 const TPyObjPtr& owner() const;
212 void setOwner(const TPyObjPtr& owner);
213 ///@}
214
215 static PyObject* iter( PyObject* iPo);
216 static PyObject* iterNext( PyObject* iPO);
217
218private:
219 TPimpl pimpl_;
220 TPyObjPtr owner_;
221};
222
223
224
225/** View that adapts a C++ Range (begin()/end()) to a PyIteratorRange.
226 *
227 * ContainerRangeView owns a ShadoweePtr to the container and produces a PyIteratorRange
228 * via `iter()`, setting the owner to the underlying container. The PyIteratorRange will
229 * iterate over `self->begin()` to `self->end()`.
230 *
231 * The main benefit of returning a ContainerRangeView over a plain PyIteratorRange is
232 * that it correctly types the iterator's value type in the generated Python stubs as
233 * `ValueType`, in contrast to the generic `Any` type used by PyIteratorRange.
234 *
235 * @tparam SelfType C++ container type
236 * @tparam ValueType Element type returned by the container's iterators
237 *
238 * @ingroup PythonIterators
239 */
240template <typename SelfType, typename ValueType>
242{
243public:
244 using TSelf = SelfType;
245 using TValue = ValueType;
246 using TSelfPtr = ShadoweePtr<TSelf>;
247 using TShadow = typename ShadoweeTraits<TSelf>::TShadow;
248 using TShadowTraits = impl::ShadowTraits<TShadow>;
249 using TShadowPtr = typename TShadowTraits::TPyClassPtr;
250
251 /** Constructor creating a range view over a container */
252 ContainerRangeView(TSelfPtr self):
253 self_(std::move(self))
254 {
255 }
256
257 std::unique_ptr<PyIteratorRange> iter() const
258 {
259 auto it = std::make_unique<PyIteratorRange>(self_->begin(), self_->end());
260 TShadowPtr owner = TShadowTraits::buildObject(self_);
261 [[maybe_unused]] TSelfPtr tmp;
262 LASS_ASSERT(TShadowTraits::getObject(owner.get(), tmp) == 0 && tmp == self_);
263 it->setOwner(owner);
264 return it;
265 }
266private:
267 TSelfPtr self_;
268};
269
270
271
272/** Returns a `ContainerRangeView` iterating over `self->begin()` to `self->end()`
273 *
274 * It automatically deduces `ValueType` from the container's iterators.
275 *
276 * This function can be used as a free method:
277 * ```
278 * PY_CLASS_FREE_METHOD_NAME(MyContainer, makeContainerRangeView<MyContainer>, methods::_iter_);
279 * ```
280 *
281 * @ingroup PythonIterators
282 * @relates ContainerRangeView
283 */
284template <typename T>
286{
287 using TValue1 = decltype(*std::declval<T>().begin());
288 using TValue2 = decltype(*std::declval<T>().end());
289 static_assert(std::is_same_v<TValue1, TValue2>, "Begin and end iterators must return the same value type");
291}
292
293
294
295/** View that adapts member iterator accessors to a PyIteratorRange.
296 *
297 * MemberRangeView holds a ShadoweePtr to the container, and obtains the iterator pair by
298 * calling the supplied member accessors `(self->*begin)()` and `(self->*end)()` when
299 * `iter()` is invoked.
300 *
301 * `iter()` produces a `PyIteratorRange` and sets `self` as its owner, keeping the container
302 * alive for the duration of the iteration.
303 *
304 * The main benefit of returning a MemberRangeView over a plain PyIteratorRange is
305 * that it correctly types the iterator's value type in the generated Python stubs as
306 * `ValueType`, in contrast to the generic `Any` type used by PyIteratorRange.
307 *
308 * @tparam SelfType C++ container type
309 * @tparam ValueType Element type returned by the container's iterators
310 * @tparam GetIterator Function type of the member iterator accessors.
311 *
312 * @ingroup PythonIterators
313 */
314template <typename SelfType, typename ValueType, typename GetIterator>
315class MemberRangeView
316{
317public:
318 using TSelf = SelfType;
319 using TValue = ValueType;
320 using TGetIterator = GetIterator;
321 using TSelfPtr = ShadoweePtr<TSelf>;
322 using TShadow = typename ShadoweeTraits<TSelf>::TShadow;
323 using TShadowTraits = impl::ShadowTraits<TShadow>;
324 using TShadowPtr = typename TShadowTraits::TPyClassPtr;
325
326 MemberRangeView(TSelfPtr self, TGetIterator begin, TGetIterator end):
327 self_(std::move(self)),
328 begin_(begin),
329 end_(end)
330 {
331 }
332
333 std::unique_ptr<PyIteratorRange> iter() const
334 {
335 auto it = std::make_unique<PyIteratorRange>(((*self_).*begin_)(), ((*self_).*end_)());
336 TShadowPtr owner = TShadowTraits::buildObject(self_);
337 [[maybe_unused]] TSelfPtr tmp;
338 LASS_ASSERT(TShadowTraits::getObject(owner.get(), tmp) == 0 && tmp == self_);
339 it->setOwner(owner);
340 return it;
341 }
342private:
343 TSelfPtr self_;
344 TGetIterator begin_;
345 TGetIterator end_;
346};
347
348
349
350/** Returns a `MemberRangeView` iterating over `(self->*begin)()` and `(self->*end)()`
351 *
352 * @ingroup PythonIterators
353 * @relates MemberRangeView
354 */
355template <typename T, typename GetIterator>
356auto makeMemberRangeView(const ShadoweePtr<T>& self, GetIterator begin, GetIterator end)
357{
358 using TGetIterator = std::decay_t<GetIterator>;
359 using TValue1 = decltype(*(std::declval<T>().*begin)());
360 using TValue2 = decltype(*(std::declval<T>().*end)());
361 static_assert(std::is_same_v<TValue1, TValue2>, "Begin and end iterators must return the same value type");
362 return MemberRangeView<T, TValue1, TGetIterator>(self, begin, end);
363}
364
365
366
367/** Returns a callable creating a `MemberRangeView` iterating over `(self->*begin)()` to `(self->*end)()`
368 *
369 * Can be used to create a free method:
370 * ```
371 * PY_CLASS_FREE_METHOD_NAME(MyContainer, makeMemberRangeViewFactory<MyContainer>(&MyContainer::begin, &MyContainer::end), methods::_iter_);
372 * ```
373 *
374 * @ingroup PythonIterators
375 * @relates FreeRangeView
376 */
377template <typename T, typename GetIterator>
378auto makeMemberRangeViewFactory(GetIterator begin, GetIterator end)
379{
380 using TSelfPtr = ShadoweePtr<T>;
381 using TGetIterator = std::decay_t<GetIterator>;
382 using TRangeView = decltype(makeMemberRangeView<T>(std::declval<TSelfPtr>(), std::declval<TGetIterator>(), std::declval<TGetIterator>()));
383 using TFactory = std::function<TRangeView(TSelfPtr)>;
384
385 TGetIterator start = begin;
386 TGetIterator stop = end;
387 TFactory makeRangeView = [start, stop](TSelfPtr self) {
388 return makeMemberRangeView<T>(self, start, stop);
389 };
390 return makeRangeView;
391}
392
393
394
395/** View that adapts free iterator accessors to a PyIteratorRange.
396 *
397 * FreeRangeView holds a ShadoweePtr to the container, and obtains the iterator pair by
398 * calling the supplied free-callable accessors `begin(*self)` and `end(*self)` when
399 * `iter()` is invoked.
400 *
401 * `iter()` produces a `PyIteratorRange` and sets `self` as its owner, keeping the container
402 * alive for the duration of the iteration.
403 *
404 * The main benefit of returning a FreeRangeView over a plain PyIteratorRange is
405 * that it correctly types the iterator's value type in the generated Python stubs as
406 * `ValueType`, in contrast to the generic `Any` type used by PyIteratorRange.
407 *
408 * @tparam SelfType C++ container type
409 * @tparam ValueType Element type returned by the container's iterators
410 * @tparam GetIterator Function type of the free-callable iterator accessors; expected to
411 * accept a reference to the container as sole argument and return an iterator.
412 *
413 * @ingroup PythonIterators
414 */
415template <typename SelfType, typename ValueType, typename GetIterator>
416class FreeRangeView
417{
418public:
419 using TSelf = SelfType;
420 using TValue = ValueType;
421 using TGetIterator = GetIterator;
422 using TSelfPtr = ShadoweePtr<TSelf>;
423 using TShadow = typename ShadoweeTraits<TSelf>::TShadow;
424 using TShadowTraits = impl::ShadowTraits<TShadow>;
425 using TShadowPtr = typename TShadowTraits::TPyClassPtr;
426
427 FreeRangeView(TSelfPtr self, GetIterator begin, GetIterator end):
428 self_(std::move(self)),
429 begin_(begin),
430 end_(end)
431 {
432 }
433
434 std::unique_ptr<PyIteratorRange> iter() const
435 {
436 auto it = std::make_unique<PyIteratorRange>(begin_(*self_), end_(*self_));
437 TShadowPtr owner = TShadowTraits::buildObject(self_);
438 [[maybe_unused]] TSelfPtr tmp;
439 LASS_ASSERT(TShadowTraits::getObject(owner.get(), tmp) == 0 && tmp == self_);
440 it->setOwner(owner);
441 return it;
442 }
443private:
444 TSelfPtr self_;
445 TGetIterator begin_;
446 TGetIterator end_;
447};
448
449
450
451/** Returns a `FreeRangeView` iterating over `begin(*self)` and `end(*self)`
452 *
453 * @ingroup PythonIterators
454 * @relates FreeRangeView
455 */
456template <typename T, typename GetIterator>
457auto makeFreeRangeView(const ShadoweePtr<T>& self, GetIterator begin, GetIterator end)
458{
459 using TGetIterator = std::decay_t<GetIterator>;
460 using TValue1 = decltype(*begin(std::declval<T&>()));
461 using TValue2 = decltype(*end(std::declval<T&>()));
462 static_assert(std::is_same_v<TValue1, TValue2>, "Begin and end iterators must return the same value type");
463 return FreeRangeView<T, TValue1, TGetIterator>(self, begin, end);
464}
465
466
467
468/** Returns a callable creating a `FreeRangeView` iterating over `begin(*self)` and `end(*self)`
469 *
470 * Can be used to create a free method:
471 * ```
472 * PY_CLASS_FREE_METHOD_NAME(MyContainer, makeFreeRangeViewFactory<MyContainer>(containerBegin, containerEnd), methods::_iter_);
473 * ```
474 *
475 * @ingroup PythonIterators
476 * @relates FreeRangeView
477 */
478template <typename T, typename GetIterator>
479auto makeFreeRangeViewFactory(GetIterator begin, GetIterator end)
480{
481 using TSelfPtr = ShadoweePtr<T>;
482 using TGetIterator = std::decay_t<GetIterator>;
483 using TRangeView = decltype(makeFreeRangeView<T>(std::declval<TSelfPtr>(), std::declval<TGetIterator>(), std::declval<TGetIterator>()));
484 using TFactory = std::function<TRangeView(TSelfPtr)>;
485
486 TGetIterator start = begin;
487 TGetIterator stop = end;
488 TFactory makeRangeView = [start, stop](TSelfPtr self) {
489 return makeFreeRangeView<T>(self, start, stop);
490 };
491 return makeRangeView;
492}
493
494
495
496/** View that adapts index-based element access to a PyIteratorRange.
497 *
498 * IndexedRangeView holds a ShadoweePtr to the container, and iterates ove the elements
499 * by index. It uses the supplied member functions `atMethod` and `sizeMethod` to access
500 * the elements and the size of the container.
501 *
502 * It retrieves the size of the container by calling `(self->*sizeMethod)()` once when the
503 * iteration starts, and accesses the elements by calling `(self->*atMethod)(index)` for
504 * each index from `0` to `size-1`.
505 *
506 * `iter()` produces a `PyIteratorRange` and sets `self` as its owner, keeping the container
507 * alive for the duration of the iteration.
508 *
509 * @tparam SelfType C++ container type
510 * @tparam ValueType Element type returned by the container's iterators
511 * @tparam SizeType Argument type of AtMethod and return type of SizeMethod
512 * @tparam AtMethod Member function type of the element accessor;
513 * expected to accept a SizeType index and return a ValueType
514 * @tparam SizeMethod Member function type of the size accessor; expected to return a SizeType
515 *
516 * @ingroup PythonIterators
517 */
518template <typename SelfType, typename ValueType, typename SizeType, typename AtMethod, typename SizeMethod>
519class IndexedRangeView
520{
521public:
522 using TSelf = SelfType;
523 using TShadowTraits = impl::ShadowTraits<typename ShadoweeTraits<TSelf>::TShadow>;
524 using TSelfPtr = ShadoweePtr<TSelf>;
525 using TShadowPtr = typename TShadowTraits::TPyClassPtr;
526
527 using TValue = ValueType;
528 using TSize = SizeType;
530
531 IndexedRangeView(TSelfPtr self, AtMethod atMethod, SizeMethod sizeMethod):
532 self_(std::move(self)),
533 atMethod_(atMethod),
534 sizeMethod_(sizeMethod)
535 {
536 }
537
538 std::unique_ptr<PyIteratorRange> iter() const
539 {
540 TSelfPtr self = self_;
541 AtMethod at = atMethod_;
542 TSize size = ((*self).*sizeMethod_)();
543 auto pimpl = std::make_unique<TImpl>(
544 [self, at](TSize index) -> TValue { return ((*self).*at)(index); },
545 size
546 );
547 auto it = std::make_unique<PyIteratorRange>(std::move(pimpl));
548 TShadowPtr owner = TShadowTraits::buildObject(self);
549 [[maybe_unused]] TSelfPtr tmp;
550 LASS_ASSERT(TShadowTraits::getObject(owner.get(), tmp) == 0 && tmp == self);
551 it->setOwner(owner);
552 return it;
553 }
554private:
555 TSelfPtr self_;
556 AtMethod atMethod_;
557 SizeMethod sizeMethod_;
558};
559
560
561/** Returns a `IndexedRangeView` iterating over `(self->*atMethod)(index)` for index in 0 to `(self->*sizeMethod)() - 1`
562 *
563 * @ingroup PythonIterators
564 * @relates IndexedRangeView
565 */
566template <typename T, typename AtMethod, typename SizeMethod>
567auto makeIndexedRangeView(const ShadoweePtr<T>& self, AtMethod atMethod, SizeMethod sizeMethod)
568{
569 using TAtMethod = std::decay_t<AtMethod>;
570 using TSizeMethod = std::decay_t<SizeMethod>;
571 using TSize = decltype((std::declval<T>().*sizeMethod)());
572 using TValue = decltype((std::declval<T>().*atMethod)(std::declval<TSize>()));
573 return IndexedRangeView<T, TValue, TSize, TAtMethod, TSizeMethod>(std::move(self), atMethod, sizeMethod);
574}
575
576
577/** Returns callable that creates a `IndexedRangeView` iterating over `(self->*atMethod)(index)` for index in 0 to `(self->*sizeMethod)() - 1`
578 *
579 * Can be used to create a free method:
580 * ```
581 * PY_CLASS_FREE_METHOD_NAME(MyContainer, makeIndexedRangeViewFactory<MyContainer>(&MyContainer::operator[], &MyContainer::size), methods::_iter_);
582 * ```
583 *
584 * @ingroup PythonIterators
585 * @relates IndexedRangeView
586 */
587template <typename T, typename AtMethod, typename SizeMethod>
588auto makeIndexedRangeViewFactory(AtMethod atMethod, SizeMethod sizeMethod)
589{
590 using TSelfPtr = ShadoweePtr<T>;
591 using TAtMethod = std::decay_t<AtMethod>;
592 using TSizeMethod = std::decay_t<SizeMethod>;
593 using TRangeView = decltype(makeIndexedRangeView<T>(std::declval<TSelfPtr>(), std::declval<TAtMethod>(), std::declval<TSizeMethod>()));
594 using TFactory = std::function<TRangeView(TSelfPtr)>;
595
596 TAtMethod at = atMethod;
597 TSizeMethod size = sizeMethod;
598 TFactory makeRangeView = [at, size](TSelfPtr self) {
599 return makeIndexedRangeView<T>(self, at, size);
600 };
601 return makeRangeView;
602}
603
604
605
606/** View that adapts index-based element access to a PyIteratorRange.
607 *
608 * IndexedRangeView holds a ShadoweePtr to the container, and iterates ove the elements
609 * by index. It uses the supplied member functions `atMethod` and `sizeMethod` to access
610 * the elements and the size of the container.
611 *
612 * It retrieves the size of the container by calling `(self->*sizeMethod)()` once when the
613 * iteration starts, and accesses the elements by calling `(self->*atMethod)(index)` for
614 * each index from `0` to `size-1`.
615 *
616 * `iter()` produces a `PyIteratorRange` and sets `self` as its owner, keeping the container
617 * alive for the duration of the iteration.
618 *
619 * @tparam SelfType C++ container type
620 * @tparam ValueType Element type returned by the container's iterators
621 * @tparam SizeType Argument type of AtMethod and return type of SizeMethod
622 * @tparam AtMethod Member function type of the element accessor;
623 * expected to accept a SizeType index and return a ValueType
624 * @tparam SizeMethod Member function type of the size accessor; expected to return a SizeType
625 *
626 * @ingroup PythonIterators
627 */
628template <typename SelfType, typename ValueType, typename SizeType, typename AtFunc, typename SizeFunc>
629class FreeIndexedRangeView
630{
631public:
632 using TSelf = SelfType;
633 using TShadowTraits = impl::ShadowTraits<typename ShadoweeTraits<TSelf>::TShadow>;
634 using TSelfPtr = ShadoweePtr<TSelf>;
635 using TShadowPtr = typename TShadowTraits::TPyClassPtr;
636
637 using TValue = ValueType;
638 using TSize = SizeType;
640
641 FreeIndexedRangeView(TSelfPtr self, AtFunc atFunc, SizeFunc sizeFunc) :
642 self_(std::move(self)),
643 atFunc_(atFunc),
644 sizeFunc_(sizeFunc)
645 {
646 }
647
648 std::unique_ptr<PyIteratorRange> iter() const
649 {
650 TSelfPtr self = self_;
651 AtFunc at = atFunc_;
652 TSize size = sizeFunc_(*self);
653 auto pimpl = std::make_unique<TImpl>(
654 [self, at](TSize index) -> TValue { return at(*self, index); },
655 size
656 );
657 auto it = std::make_unique<PyIteratorRange>(std::move(pimpl));
658 TShadowPtr owner = TShadowTraits::buildObject(self);
659 [[maybe_unused]] TSelfPtr tmp;
660 LASS_ASSERT(TShadowTraits::getObject(owner.get(), tmp) == 0 && tmp == self);
661 it->setOwner(owner);
662 return it;
663 }
664private:
665 TSelfPtr self_;
666 AtFunc atFunc_;
667 SizeFunc sizeFunc_;
668};
669
670
671/** Returns a `FreeIndexedRangeView` iterating over `atFunc(*self, index)` for index in 0 to `sizeFunc(*self) - 1`
672 *
673 * @ingroup PythonIterators
674 * @relates FreeIndexedRangeView
675 */
676template <typename T, typename AtFunc, typename SizeFunc>
677auto makeFreeIndexedRangeView(const ShadoweePtr<T>& self, AtFunc atFunc, SizeFunc sizeFunc)
678{
679 using TSize = decltype(sizeFunc(std::declval<T&>()));
680 using TValue = decltype(atFunc(std::declval<T&>(), std::declval<TSize>()));
681 return FreeIndexedRangeView<T, TValue, TSize, AtFunc, SizeFunc>(std::move(self), atFunc, sizeFunc);
682}
683
684
685/** Returns a callable that creates `FreeIndexedRangeView` iterating over `atFunc(*self, index)` for index in 0 to `sizeFunc(*self) - 1`
686 *
687 * Can be used to create a free method:
688 * ```
689 * PY_CLASS_FREE_METHOD_NAME(MyContainer, makeFreeIndexedRangeViewFactory<MyContainer>(myContainerAt, myContainerSize), methods::_iter_);
690 * ```
691 *
692 * @ingroup PythonIterators
693 * @relates FreeIndexedRangeView
694 */
695template <typename T, typename AtFunc, typename SizeFunc>
696auto makeFreeIndexedRangeViewFactory(AtFunc atFunc, SizeFunc sizeFunc)
697{
698 using TSelfPtr = ShadoweePtr<T>;
699 using TAtFunc = std::decay_t<AtFunc>;
700 using TSizeFunc = std::decay_t<SizeFunc>;
701 using TRangeView = decltype(makeFreeIndexedRangeView<T>(std::declval<TSelfPtr>(), std::declval<TAtFunc>(), std::declval<TSizeFunc>()));
702 using TFactory = std::function<TRangeView(TSelfPtr)>;
703
704 TAtFunc at = atFunc;
705 TSizeFunc size = sizeFunc;
706 TFactory makeRangeView = [at, size](TSelfPtr self) {
707 return makeFreeIndexedRangeView<T>(self, at, size);
708 };
709 return makeRangeView;
710}
711
712
713
714
715/** Passes a PyIteratorRange to Python
716 *
717 * @note This steals a reference to the PyIteratorRange object.
718 *
719 * @ingroup PythonIterators PyExportTraits
720 */
721template<>
723{
724 constexpr static const char* py_typing = "Iterator[Any]";
725 constexpr static const char* py_typing_preamble = "from collections.abc import Iterator";
726
727 /** we take ownership! */
728 static PyObject* build( PyIteratorRange* iV )
729 {
730 return iV;
731 }
732};
733
734
735template <typename RangeViewType, typename ValueType>
736struct PyExportTraitsRangeView
737{
738 constexpr static const char* py_typing = "Iterator[ValueType]";
739 constexpr static const char* py_typing_preamble = "from collections.abc import Iterator";
740
741 static PyObject* build(const RangeViewType& value)
742 {
743 return value.iter().release();
744 }
745};
746
747
748/** Builds a PyIteratorRange from a ContainerRangeView
749 *
750 * @ingroup PythonIterators PyExportTraits
751 * @relates ContainerRangeView
752 */
753template <typename SelfType, typename ValueType>
754struct PyExportTraits<ContainerRangeView<SelfType, ValueType>>:
755 PyExportTraitsRangeView<ContainerRangeView<SelfType, ValueType>, ValueType>
756{
757};
758
759
760/** Builds a PyIteratorRange from a MemberRangeView
761 *
762 * @ingroup PythonIterators PyExportTraits
763 * @relates MemberRangeView
764 */
765template <typename SelfType, typename ValueType, typename GetIterator>
766struct PyExportTraits<MemberRangeView<SelfType, ValueType, GetIterator>>:
767 PyExportTraitsRangeView<MemberRangeView<SelfType, ValueType, GetIterator>, ValueType>
768{
769};
770
771
772/** Builds a PyIteratorRange from a FreeRangeView
773 *
774 * @ingroup PythonIterators PyExportTraits
775 * @relates FreeRangeView
776 */
777template <typename SelfType, typename ValueType, typename GetIterator>
778struct PyExportTraits< FreeRangeView<SelfType, ValueType, GetIterator> >:
779 PyExportTraitsRangeView<FreeRangeView<SelfType, ValueType, GetIterator>, ValueType>
780{
781};
782
783
784/** Builds a PyIteratorRange from a IndexedRangeView
785 *
786 * @ingroup PythonIterators PyExportTraits
787 * @relates IndexedRangeView
788 */
789template <typename SelfType, typename ValueType, typename SizeType, typename AtMethod, typename SizeMethod>
790struct PyExportTraits< IndexedRangeView<SelfType, ValueType, SizeType, AtMethod, SizeMethod> >:
791 PyExportTraitsRangeView<IndexedRangeView<SelfType, ValueType, SizeType, AtMethod, SizeMethod>, ValueType>
792{
793};
794
795
796/** Builds a PyIteratorRange from a FreeIndexedRangeView
797 *
798 * @ingroup PythonIterators PyExportTraits
799 * @relates FreeIndexedRangeView
800 */
801template <typename SelfType, typename ValueType, typename SizeType, typename AtFunc, typename SizeFunc>
802struct PyExportTraits< FreeIndexedRangeView<SelfType, ValueType, SizeType, AtFunc, SizeFunc> > :
803 PyExportTraitsRangeView<FreeIndexedRangeView<SelfType, ValueType, SizeType, AtFunc, SizeFunc>, ValueType>
804{
805};
806
807
808}
809
810}
811
812// --- iterators -------------------------------------------------------------------------------------
813
814/** @ingroup PythonIterators
815 * Exports a set of C++ iterators to Python
816 *
817 * @param t_cppClass
818 * the C++ class you're exporting an iterator of
819 * @param i_cppBegin
820 * the name of the method in C++ that provides the beginning of the exported iterator range
821 * @param i_cppEnd
822 * the name of the method in C++ that provides the beginning of the exported iterator range
823 * @param s_doc
824 * documentation of method as shown in Python (zero terminated C string)
825 * @param i_dispatcher
826 * A unique name of the static C++ dispatcher function to be generated. This name will be
827 * used for the names of automatic generated variables and functions and should be unique
828 * per exported C++ class/method pair.
829 *
830 * Invoke this macro to export an iterator range to python. The returned object will support the
831 * iterator protocol by default. The class generating the export will also support the iterator
832 * generator protocol.
833 *
834 * @note
835 * the documentation of the Python method will be the @a s_doc of the first exported
836 * overload.
837 * @note
838 * the iterator stability of C++ is inherited into Python. It is the responsibility of the user
839 * of these macro's to ensure or document the stability of the iterators.
840 * @note
841 * Although you can overload the __iter__ slot it is probably of little use. The last assigned function
842 * will return the iterator. It could be useful if some of the overloaded function throw, in that case
843 * the first non-throwing (if any) function will be chosen.
844 *
845 * @code
846 * // foo.h
847 * class Foo
848 * {
849 * PY_HEADER(python::PyObjectPlus)
850 * public:
851 * void barA(int a);
852 * std::vector<int>::const_iterator begin() const { return vector_.begin(); }
853 * std::vector<int>::const_iterator end() const { return vector_.end(); }
854 * private:
855 * std::vector<int> vector_;
856 * };
857 *
858 * std::vector<int> temp;
859 * std::vector<int>::const_iterator freeBegin(Foo& iObj) { return temp.begin(); }
860 * std::vector<int>::const_iterator freeEnd(Foo& iObj) { return temp.end(); }
861 *
862 * // foo.cpp
863 * PY_DECLARE_CLASS(Foo)
864 * PY_CLASS_ITERFUNC_EX(Foo, begin, end, 0, foo_bar_a)
865 * PY_CLASS_FREE_ITERFUNC_EX(Foo, begin, end, 0, foo_bar_a)
866 * @endcode
867 */
868#define PY_CLASS_ITERFUNC_EX( t_cppClass, i_cppBegin, i_cppEnd, s_doc, i_dispatcher )\
869 lass::python::PyIteratorRange* LASS_CONCATENATE_3( lassPyImpl_method_, i_dispatcher, itDispatch1 ) (const ::lass::python::impl::ShadowTraits< t_cppClass >::TCppClassPtr& iObj) { \
870 return new lass::python::PyIteratorRange(iObj->i_cppBegin (), iObj->i_cppEnd ()); } \
871 PY_CLASS_FREE_METHOD_NAME_DOC( t_cppClass, LASS_CONCATENATE_3( lassPyImpl_method_, i_dispatcher, itDispatch1 ), lass::python::methods::_iter_, s_doc)
872/** @ingroup PythonIterators
873 * convenience macro, wraps PY_CLASS_ITERFUNC_EX with
874 * @a i_dispatcher = lassPyImpl_method_ ## @a i_cppClass ## __LINE__.
875 */
876#define PY_CLASS_ITERFUNC_DOC( i_cppClass, i_cppBegin, icppEnd, s_doc )\
877 PY_CLASS_ITERFUNC_EX(\
878 i_cppClass, i_cppBegin, icppEnd, s_doc,\
879 LASS_UNIQUENAME(LASS_CONCATENATE(lassPyImpl_method_, i_cppClass)))
880/** @ingroup PythonIterators
881 * convenience macro, wraps PY_CLASS_ITERFUNC_DOC with @a s_doc = 0.
882 */
883#define PY_CLASS_ITERFUNC( i_cppClass, i_cppBegin, icppEnd )\
884 PY_CLASS_ITERFUNC_DOC( i_cppClass, i_cppBegin, icppEnd, 0 )
885
886#define PY_CLASS_FREE_ITERFUNC_EX( t_cppClass, i_cppBegin, i_cppEnd, s_doc, i_dispatcher )\
887lass::python::PyIteratorRange* LASS_CONCATENATE_3( lassPyImpl_method_, i_dispatcher, itDispatch2 ) (const ::lass::python::impl::ShadowTraits< t_cppClass >::TCppClassPtr& iObj) { \
888return new lass::python::PyIteratorRange(i_cppBegin(iObj), i_cppEnd(iObj)); } \
889 PY_CLASS_FREE_METHOD_NAME_DOC( t_cppClass, LASS_CONCATENATE_3( lassPyImpl_method_, i_dispatcher, itDispatch2 ), lass::python::methods::_iter_, s_doc)
890/** @ingroup PythonIterators
891 * convenience macro, wraps PY_CLASS_ITERFUNC_EX with
892 * @a i_dispatcher = lassPyImpl_method_ ## @a i_cppClass ## __LINE__.
893 */
894#define PY_CLASS_FREE_ITERFUNC_DOC( i_cppClass, i_cppBegin, icppEnd, s_doc )\
895 PY_CLASS_FREE_ITERFUNC_EX(\
896 i_cppClass, i_cppBegin, icppEnd, s_doc,\
897 LASS_UNIQUENAME(LASS_CONCATENATE(lassPyImpl_method_, i_cppClass)))
898/** @ingroup PythonIterators
899 * convenience macro, wraps PY_CLASS_ITERFUNC_DOC with @a s_doc = 0.
900 */
901#define PY_CLASS_FREE_ITERFUNC( i_cppClass, i_cppBegin, icppEnd )\
902 PY_CLASS_FREE_ITERFUNC_DOC( i_cppClass, i_cppBegin, icppEnd, 0 )
903
904#if LASS_COMPILER_TYPE == LASS_COMPILER_TYPE_MSVC
905# pragma warning(pop)
906#endif
907
908#endif
View that adapts a C++ Range (begin()/end()) to a PyIteratorRange.
ContainerRangeView(TSelfPtr self)
Constructor creating a range view over a container.
View that adapts index-based element access to a PyIteratorRange.
View that adapts free iterator accessors to a PyIteratorRange.
View that adapts index-based element access to a PyIteratorRange.
View that adapts member iterator accessors to a PyIteratorRange.
Python iterator object.
PyIteratorRange(TPimpl pimpl)
Construct PyIteratorRange with custom implementation.
PyIteratorRange(Iterator first, Iterator last)
Construct PyIteratorRange with default implemenation iterating between two iterators.
Implementation of PyIteratorRangeImplBase to iterate over a sequence by index.
auto makeFreeRangeViewFactory(GetIterator begin, GetIterator end)
Returns a callable creating a FreeRangeView iterating over begin(*self) and end(*self)
auto makeIndexedRangeViewFactory(AtMethod atMethod, SizeMethod sizeMethod)
Returns callable that creates a IndexedRangeView iterating over (self->*atMethod)(index) for index in...
auto makeMemberRangeView(const ShadoweePtr< T > &self, GetIterator begin, GetIterator end)
Returns a MemberRangeView iterating over (self->*begin)() and (self->*end)()
auto makeFreeIndexedRangeViewFactory(AtFunc atFunc, SizeFunc sizeFunc)
Returns a callable that creates FreeIndexedRangeView iterating over atFunc(*self, index) for index in...
auto makeMemberRangeViewFactory(GetIterator begin, GetIterator end)
Returns a callable creating a MemberRangeView iterating over (self->*begin)() to (self->*end)()
auto makeIndexedRangeView(const ShadoweePtr< T > &self, AtMethod atMethod, SizeMethod sizeMethod)
Returns a IndexedRangeView iterating over (self->*atMethod)(index) for index in 0 to (self->*sizeMeth...
auto makeFreeIndexedRangeView(const ShadoweePtr< T > &self, AtFunc atFunc, SizeFunc sizeFunc)
Returns a FreeIndexedRangeView iterating over atFunc(*self, index) for index in 0 to sizeFunc(*self) ...
auto makeFreeRangeView(const ShadoweePtr< T > &self, GetIterator begin, GetIterator end)
Returns a FreeRangeView iterating over begin(*self) and end(*self)
auto makeContainerRangeView(const ShadoweePtr< T > &self)
Returns a ContainerRangeView iterating over self->begin() to self->end()
PyObjectPtr< PyObject >::Type TPyObjPtr
PyObjectPtr to a PyObject.
#define PY_HEADER(t_parentClass)
Place as first line of your Pythonized class.
std::conditional_t< std::is_const_v< ShadoweeType >, typename impl::ShadowTraits< typename ShadoweeTraits< ShadoweeType >::TShadow >::TConstCppClassPtr, typename impl::ShadowTraits< typename ShadoweeTraits< ShadoweeType >::TShadow >::TCppClassPtr > ShadoweePtr
Helper to get the pointer type holding the shadowee in a shadow object.
Comprehensive C++ to Python binding library.
Library for Assembled Shared Sources.
Definition config.h:53
static PyObject * build(PyIteratorRange *iV)
we take ownership!
by copy, general case assumes shadow type or PyObjectPlus based type.