Skip to content

Commit 628ac3e

Browse files
try to take advantage of existing infra
1 parent a0a6a5f commit 628ac3e

File tree

2 files changed

+45
-39
lines changed

2 files changed

+45
-39
lines changed

include/pybind11/cast.h

Lines changed: 4 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -759,6 +759,10 @@ template <typename T> struct handle_type_name { static constexpr auto name = _<T
759759
template <> struct handle_type_name<bytes> { static constexpr auto name = _(PYBIND11_BYTES_NAME); };
760760
template <> struct handle_type_name<int_> { static constexpr auto name = _("int"); };
761761
template <> struct handle_type_name<iterable> { static constexpr auto name = _("Iterable"); };
762+
template <typename T>
763+
struct handle_type_name<iterable_t<T>> {
764+
static constexpr auto name = _("Iterable[") + type_caster<T>::name + _("]");
765+
}
762766
template <> struct handle_type_name<iterator> { static constexpr auto name = _("Iterator"); };
763767
template <> struct handle_type_name<none> { static constexpr auto name = _("None"); };
764768
template <> struct handle_type_name<args> { static constexpr auto name = _("*args"); };
@@ -795,36 +799,6 @@ struct pyobject_caster {
795799
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name);
796800
};
797801

798-
// Specialize for iterator_t<T>.
799-
template <typename T>
800-
struct iterable_t_type_caster : type_caster<py::iterable> {
801-
using Base = type_caster<py::iterable>;
802-
using MyType = iterable_t<T>;
803-
804-
// Python to C++.
805-
bool load(handle src, bool convert) {
806-
if (!isinstance<py::iterable>(src)) {
807-
return false;
808-
}
809-
for (handle src_i : src) {
810-
// TODO: Would be nice if we could use something like
811-
// `check(h, convert)` where nothing is necessarily converted.
812-
if (!isinstance<T>(src_i)) {
813-
return false;
814-
}
815-
}
816-
return Base::load(src, convert);
817-
}
818-
819-
static constexpr auto name = _("Iterable[") + type_caster<T>::name + _("]");
820-
operator MyType() {
821-
py::iterable h = static_cast<py::iterable>(*static_cast<Base*>(this));
822-
return MyType(h);
823-
}
824-
template <typename U>
825-
using cast_op_type = MyType;
826-
};
827-
828802
template <typename T>
829803
class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caster<T> { };
830804

include/pybind11/pytypes.h

Lines changed: 41 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -763,6 +763,39 @@ inline bool PyIterable_Check(PyObject *obj) {
763763
}
764764
}
765765

766+
template <typename T>
767+
bool PyIterableT_Check(PyObject *obj) {
768+
static_assert(
769+
is_generic_type<T>::value || is_pyobject<T>::value,
770+
"iterable_t can only be used with pyobjects and generic types "
771+
"(py::class_<T>");
772+
PyObject *iter = PyObject_GetIter(obj);
773+
if (iter) {
774+
if (iter == obj) {
775+
// If they are the same, then that's bad! For now, just throw a
776+
// cast error.
777+
Py_DECREF(iter);
778+
throw cast_error(
779+
"iterable_t<T> cannot be used with exhausitble iterables "
780+
"(e.g., iterators, generators).");
781+
}
782+
bool good = true;
783+
// Now that we know that the iterable `obj` will not be exhausted,
784+
// let's check the contained types.
785+
for (handle h : handle(iter)) {
786+
if (!isinstance<T>(h)) {
787+
good = false;
788+
break;
789+
}
790+
}
791+
Py_DECREF(iter);
792+
return good;
793+
} else {
794+
PyErr_Clear();
795+
return false;
796+
}
797+
}
798+
766799
inline bool PyNone_Check(PyObject *o) { return o == Py_None; }
767800
inline bool PyEllipsis_Check(PyObject *o) { return o == Py_Ellipsis; }
768801

@@ -953,17 +986,16 @@ class iterable : public object {
953986

954987
/// Provides similar interface to `iterable`, but constraining the intended
955988
/// type.
956-
/// @warning Due to usage of `isinstance<T>()` in the related type-caster, this
957-
/// does *not* work for iterables of type-converted values (e.g. `int`).
989+
/// @warning Due to technical reasons, this is constrained in two ways:
990+
/// - Due to how `isinstance<T>()` works, this does *not* work for iterables of
991+
/// type-converted values (e.g. `int`).
992+
/// - Because we must check the contained types within the iterable (for
993+
/// overloads), we must iterate through the iterable. For this reason, the
994+
/// iterable should *not* be exhaustible (e.g., iterator, generator).
958995
template <typename T>
959-
class iterable_t : public py::iterable {
996+
class iterable_t : public iterable {
960997
public:
961-
// See type_caster specialization.
962-
static_assert(
963-
is_generic_type<T>::value || is_pyobject<T>::value,
964-
"iterable_t can only be used with pyobjects and generic types "
965-
"(py::class_<T>");
966-
using py::iterable::iterable;
998+
PYBIND11_OBJECT_DEFAULT(iterable_t, iterable, detail::PyIterableT_Check<T>)
967999
};
9681000

9691001
class bytes;

0 commit comments

Comments
 (0)