Skip to content

Commit a0a6a5f

Browse files
WIP pytypes: Add iterable_t<>
1 parent 6709abb commit a0a6a5f

File tree

2 files changed

+70
-2
lines changed

2 files changed

+70
-2
lines changed

include/pybind11/cast.h

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,18 @@ PYBIND11_NAMESPACE_BEGIN(detail)
5050
template <typename type, typename SFINAE = void> class type_caster : public type_caster_base<type> { };
5151
template <typename type> using make_caster = type_caster<intrinsic_t<type>>;
5252

53+
template <
54+
typename T,
55+
enable_if_t<std::is_base<type_caster_generic, make_caster<T>
56+
>
57+
struct is_generic_type : public std::true_type {};
58+
59+
template <
60+
typename T,
61+
enable_if_t<!std::is_base<type_caster_generic, make_caster<T>
62+
>
63+
struct is_generic_type : public std::false_type {};
64+
5365
// Shortcut for calling a caster's `cast_op_type` cast operator for casting a type_caster to a T
5466
template <typename T> typename make_caster<T>::template cast_op_type<T> cast_op(make_caster<T> &caster) {
5567
return caster.operator typename make_caster<T>::template cast_op_type<T>();
@@ -752,6 +764,7 @@ template <> struct handle_type_name<none> { static constexpr auto name = _("None
752764
template <> struct handle_type_name<args> { static constexpr auto name = _("*args"); };
753765
template <> struct handle_type_name<kwargs> { static constexpr auto name = _("**kwargs"); };
754766

767+
755768
template <typename type>
756769
struct pyobject_caster {
757770
template <typename T = type, enable_if_t<std::is_same<T, handle>::value, int> = 0>
@@ -782,6 +795,36 @@ struct pyobject_caster {
782795
PYBIND11_TYPE_CASTER(type, handle_type_name<type>::name);
783796
};
784797

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+
785828
template <typename T>
786829
class type_caster<T, enable_if_t<is_pyobject<T>::value>> : public pyobject_caster<T> { };
787830

include/pybind11/pytypes.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,11 @@ PYBIND11_NAMESPACE_BEGIN(detail)
2626
class args_proxy;
2727
inline bool isinstance_generic(handle obj, const std::type_info &tp);
2828

29+
// Indicates that type is generic and and does not have a specialized
30+
// `type_caster<>` specialization. Defined in `cast.h`.
31+
template <typename T, typename SFINAE>
32+
struct is_generic_type;
33+
2934
// Accessor forward declarations
3035
template <typename Policy> class accessor;
3136
namespace accessor_policies {
@@ -380,13 +385,18 @@ class error_already_set : public std::runtime_error {
380385
/** \ingroup python_builtins
381386
\rst
382387
Return true if ``obj`` is an instance of ``T``. Type ``T`` must be a subclass of
383-
`object` or a class which was exposed to Python as ``py::class_<T>``.
388+
`object` or a class which was exposed to Python as ``py::class_<T>`` (generic).
384389
\endrst */
385390
template <typename T, detail::enable_if_t<std::is_base_of<object, T>::value, int> = 0>
386391
bool isinstance(handle obj) { return T::check_(obj); }
387392

388393
template <typename T, detail::enable_if_t<!std::is_base_of<object, T>::value, int> = 0>
389-
bool isinstance(handle obj) { return detail::isinstance_generic(obj, typeid(T)); }
394+
bool isinstance(handle obj) {
395+
static_assert(
396+
is_generic_type<T>::value,
397+
"isisntance<T>() requires specialization for this type");
398+
return detail::isinstance_generic(obj, typeid(T));
399+
}
390400

391401
template <> inline bool isinstance<handle>(handle) = delete;
392402
template <> inline bool isinstance<object>(handle obj) { return obj.ptr() != nullptr; }
@@ -941,6 +951,21 @@ class iterable : public object {
941951
PYBIND11_OBJECT_DEFAULT(iterable, object, detail::PyIterable_Check)
942952
};
943953

954+
/// Provides similar interface to `iterable`, but constraining the intended
955+
/// 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`).
958+
template <typename T>
959+
class iterable_t : public py::iterable {
960+
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;
967+
};
968+
944969
class bytes;
945970

946971
class str : public object {

0 commit comments

Comments
 (0)