mirror of
https://github.com/pybind/pybind11.git
synced 2025-02-16 21:57:55 +00:00
Allow passing base types as a template parameter
This allows a slightly cleaner base type specification of: py::class_<Type, Base>("Type") as an alternative to py::class_<Type>("Type", py::base<Base>()) As with the other template parameters, the order relative to the holder or trampoline types doesn't matter. This also includes a compile-time assertion failure if attempting to specify more than one base class (but is easily extendible to support multiple inheritance, someday, by updating the class_selector::set_bases function to set multiple bases).
This commit is contained in:
parent
5fffe200e3
commit
6b52c838d7
@ -1227,7 +1227,7 @@ section.
|
|||||||
the other existing exception translators.
|
the other existing exception translators.
|
||||||
|
|
||||||
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
|
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
|
||||||
be used as a ``py::base``.
|
be used as a base type.
|
||||||
|
|
||||||
.. _eigen:
|
.. _eigen:
|
||||||
|
|
||||||
@ -1811,16 +1811,17 @@ However, it can be acquired as follows:
|
|||||||
.def(py::init<const std::string &>())
|
.def(py::init<const std::string &>())
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
Alternatively, we can rely on the ``base`` tag, which performs an automated
|
Alternatively, you can specify the base class as a template parameter option to
|
||||||
lookup of the corresponding Python type. However, this also requires invoking
|
``class_``, which performs an automated lookup of the corresponding Python
|
||||||
the ``import`` function once to ensure that the pybind11 binding code of the
|
type. Like the above code, however, this also requires invoking the ``import``
|
||||||
module ``basic`` has been executed.
|
function once to ensure that the pybind11 binding code of the module ``basic``
|
||||||
|
has been executed:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
py::module::import("basic");
|
py::module::import("basic");
|
||||||
|
|
||||||
py::class_<Dog>(m, "Dog", py::base<Pet>())
|
py::class_<Dog, Pet>(m, "Dog")
|
||||||
.def(py::init<const std::string &>())
|
.def(py::init<const std::string &>())
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
|
@ -185,9 +185,10 @@ inheritance relationship:
|
|||||||
std::string bark() const { return "woof!"; }
|
std::string bark() const { return "woof!"; }
|
||||||
};
|
};
|
||||||
|
|
||||||
There are two different ways of indicating a hierarchical relationship to
|
There are three different ways of indicating a hierarchical relationship to
|
||||||
pybind11: the first is by specifying the C++ base class explicitly during
|
pybind11: the first specifies the C++ base class as an extra template
|
||||||
construction using the ``base`` attribute:
|
parameter of the :class:`class_`; the second uses a special ``base`` attribute
|
||||||
|
passed into the constructor:
|
||||||
|
|
||||||
.. code-block:: cpp
|
.. code-block:: cpp
|
||||||
|
|
||||||
@ -195,6 +196,12 @@ construction using the ``base`` attribute:
|
|||||||
.def(py::init<const std::string &>())
|
.def(py::init<const std::string &>())
|
||||||
.def_readwrite("name", &Pet::name);
|
.def_readwrite("name", &Pet::name);
|
||||||
|
|
||||||
|
// Method 1: template parameter:
|
||||||
|
py::class_<Dog, Pet /* <- specify C++ parent type */>(m, "Dog")
|
||||||
|
.def(py::init<const std::string &>())
|
||||||
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
|
// Method 2: py::base attribute:
|
||||||
py::class_<Dog>(m, "Dog", py::base<Pet>() /* <- specify C++ parent type */)
|
py::class_<Dog>(m, "Dog", py::base<Pet>() /* <- specify C++ parent type */)
|
||||||
.def(py::init<const std::string &>())
|
.def(py::init<const std::string &>())
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
@ -208,11 +215,12 @@ Alternatively, we can also assign a name to the previously bound ``Pet``
|
|||||||
pet.def(py::init<const std::string &>())
|
pet.def(py::init<const std::string &>())
|
||||||
.def_readwrite("name", &Pet::name);
|
.def_readwrite("name", &Pet::name);
|
||||||
|
|
||||||
|
// Method 3: pass parent class_ object:
|
||||||
py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */)
|
py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */)
|
||||||
.def(py::init<const std::string &>())
|
.def(py::init<const std::string &>())
|
||||||
.def("bark", &Dog::bark);
|
.def("bark", &Dog::bark);
|
||||||
|
|
||||||
Functionality-wise, both approaches are completely equivalent. Afterwards,
|
Functionality-wise, all three approaches are completely equivalent. Afterwards,
|
||||||
instances will expose fields and methods of both types:
|
instances will expose fields and methods of both types:
|
||||||
|
|
||||||
.. code-block:: pycon
|
.. code-block:: pycon
|
||||||
|
@ -798,12 +798,12 @@ protected:
|
|||||||
holder_type holder;
|
holder_type holder;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// PYBIND11_DECLARE_HOLDER_TYPE holder types:
|
||||||
template <typename base, typename holder> struct is_holder_type :
|
template <typename base, typename holder> struct is_holder_type :
|
||||||
// PYBIND11_DECLARE_HOLDER_TYPE holder types:
|
std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
|
||||||
std::conditional<std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>>::value,
|
// Specialization for always-supported unique_ptr holders:
|
||||||
std::true_type,
|
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> :
|
||||||
std::false_type>::type {};
|
std::true_type {};
|
||||||
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
|
|
||||||
|
|
||||||
template <typename T> struct handle_type_name { static PYBIND11_DESCR name() { return _<T>(); } };
|
template <typename T> struct handle_type_name { static PYBIND11_DESCR name() { return _<T>(); } };
|
||||||
template <> struct handle_type_name<bytes> { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } };
|
template <> struct handle_type_name<bytes> { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } };
|
||||||
|
@ -358,20 +358,20 @@ template <template<typename> class P, typename T, typename... Ts>
|
|||||||
struct any_of_t<P, T, Ts...> : conditional_t<P<T>::value, std::true_type, any_of_t<P, Ts...>> { };
|
struct any_of_t<P, T, Ts...> : conditional_t<P<T>::value, std::true_type, any_of_t<P, Ts...>> { };
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
// Extracts the first type from the template parameter pack matching the predicate, or void if none match.
|
// Extracts the first type from the template parameter pack matching the predicate, or Default if none match.
|
||||||
template <template<class> class Predicate, class... Ts> struct first_of;
|
template <template<class> class Predicate, class Default, class... Ts> struct first_of;
|
||||||
template <template<class> class Predicate> struct first_of<Predicate> {
|
template <template<class> class Predicate, class Default> struct first_of<Predicate, Default> {
|
||||||
using type = void;
|
using type = Default;
|
||||||
};
|
};
|
||||||
template <template<class> class Predicate, class T, class... Ts>
|
template <template<class> class Predicate, class Default, class T, class... Ts>
|
||||||
struct first_of<Predicate, T, Ts...> {
|
struct first_of<Predicate, Default, T, Ts...> {
|
||||||
using type = typename std::conditional<
|
using type = typename std::conditional<
|
||||||
Predicate<T>::value,
|
Predicate<T>::value,
|
||||||
T,
|
T,
|
||||||
typename first_of<Predicate, Ts...>::type
|
typename first_of<Predicate, Default, Ts...>::type
|
||||||
>::type;
|
>::type;
|
||||||
};
|
};
|
||||||
template <template<class> class Predicate, class... T> using first_of_t = typename first_of<Predicate, T...>::type;
|
template <template<class> class Predicate, class Default, class... T> using first_of_t = typename first_of<Predicate, Default, T...>::type;
|
||||||
|
|
||||||
// Counts the number of types in the template parameter pack matching the predicate
|
// Counts the number of types in the template parameter pack matching the predicate
|
||||||
template <template<typename> class Predicate, typename... Ts> struct count_t;
|
template <template<typename> class Predicate, typename... Ts> struct count_t;
|
||||||
|
@ -802,34 +802,47 @@ protected:
|
|||||||
|
|
||||||
static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; }
|
static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
template <template<typename> class Predicate, typename... BaseTypes> struct class_selector;
|
||||||
|
template <template<typename> class Predicate, typename Base, typename... Bases>
|
||||||
|
struct class_selector<Predicate, Base, Bases...> {
|
||||||
|
static inline void set_bases(detail::type_record &record) {
|
||||||
|
if (Predicate<Base>::value) record.base_type = &typeid(Base);
|
||||||
|
else class_selector<Predicate, Bases...>::set_bases(record);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
template <template<typename> class Predicate>
|
||||||
|
struct class_selector<Predicate> {
|
||||||
|
static inline void set_bases(detail::type_record &) {}
|
||||||
|
};
|
||||||
|
|
||||||
NAMESPACE_END(detail)
|
NAMESPACE_END(detail)
|
||||||
|
|
||||||
template <typename type_, typename... options>
|
template <typename type_, typename... options>
|
||||||
class class_ : public detail::generic_type {
|
class class_ : public detail::generic_type {
|
||||||
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
|
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
|
||||||
template <typename T> using is_subtype = detail::bool_constant<std::is_base_of<type_, T>::value && !std::is_same<T, type_>::value>;
|
template <typename T> using is_subtype = detail::bool_constant<std::is_base_of<type_, T>::value && !std::is_same<T, type_>::value>;
|
||||||
|
template <typename T> using is_base_class = detail::bool_constant<std::is_base_of<T, type_>::value && !std::is_same<T, type_>::value>;
|
||||||
template <typename T> using is_valid_class_option =
|
template <typename T> using is_valid_class_option =
|
||||||
detail::bool_constant<
|
detail::bool_constant<
|
||||||
is_holder<T>::value ||
|
is_holder<T>::value ||
|
||||||
is_subtype<T>::value
|
is_subtype<T>::value ||
|
||||||
|
is_base_class<T>::value
|
||||||
>;
|
>;
|
||||||
|
|
||||||
using extracted_holder_t = typename detail::first_of_t<is_holder, options...>;
|
|
||||||
|
|
||||||
public:
|
public:
|
||||||
using type = type_;
|
using type = type_;
|
||||||
using type_alias = detail::first_of_t<is_subtype, options...>;
|
using type_alias = detail::first_of_t<is_subtype, void, options...>;
|
||||||
constexpr static bool has_alias = !std::is_void<type_alias>::value;
|
constexpr static bool has_alias = !std::is_void<type_alias>::value;
|
||||||
using holder_type = typename std::conditional<
|
using holder_type = detail::first_of_t<is_holder, std::unique_ptr<type>, options...>;
|
||||||
std::is_void<extracted_holder_t>::value,
|
|
||||||
std::unique_ptr<type>,
|
|
||||||
extracted_holder_t
|
|
||||||
>::type;
|
|
||||||
using instance_type = detail::instance<type, holder_type>;
|
using instance_type = detail::instance<type, holder_type>;
|
||||||
|
|
||||||
static_assert(detail::all_of_t<is_valid_class_option, options...>::value,
|
static_assert(detail::all_of_t<is_valid_class_option, options...>::value,
|
||||||
"Unknown/invalid class_ template parameters provided");
|
"Unknown/invalid class_ template parameters provided");
|
||||||
|
|
||||||
|
static_assert(detail::count_t<is_base_class, options...>::value <= 1,
|
||||||
|
"Invalid class_ base types: multiple inheritance is not supported");
|
||||||
|
|
||||||
PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check)
|
PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check)
|
||||||
|
|
||||||
template <typename... Extra>
|
template <typename... Extra>
|
||||||
@ -843,6 +856,8 @@ public:
|
|||||||
record.init_holder = init_holder;
|
record.init_holder = init_holder;
|
||||||
record.dealloc = dealloc;
|
record.dealloc = dealloc;
|
||||||
|
|
||||||
|
detail::class_selector<is_base_class, options...>::set_bases(record);
|
||||||
|
|
||||||
/* Process optional arguments, if any */
|
/* Process optional arguments, if any */
|
||||||
detail::process_attributes<Extra...>::init(extra..., &record);
|
detail::process_attributes<Extra...>::init(extra..., &record);
|
||||||
|
|
||||||
|
@ -31,6 +31,11 @@ public:
|
|||||||
Rabbit(const std::string &name) : Pet(name, "parrot") {}
|
Rabbit(const std::string &name) : Pet(name, "parrot") {}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class Hamster : public Pet {
|
||||||
|
public:
|
||||||
|
Hamster(const std::string &name) : Pet(name, "rodent") {}
|
||||||
|
};
|
||||||
|
|
||||||
std::string pet_name_species(const Pet &pet) {
|
std::string pet_name_species(const Pet &pet) {
|
||||||
return pet.name() + " is a " + pet.species();
|
return pet.name() + " is a " + pet.species();
|
||||||
}
|
}
|
||||||
@ -59,6 +64,10 @@ test_initializer inheritance([](py::module &m) {
|
|||||||
py::class_<Rabbit>(m, "Rabbit", py::base<Pet>())
|
py::class_<Rabbit>(m, "Rabbit", py::base<Pet>())
|
||||||
.def(py::init<std::string>());
|
.def(py::init<std::string>());
|
||||||
|
|
||||||
|
/* And another: list parent in class template arguments */
|
||||||
|
py::class_<Hamster, Pet>(m, "Hamster")
|
||||||
|
.def(py::init<std::string>());
|
||||||
|
|
||||||
m.def("pet_name_species", pet_name_species);
|
m.def("pet_name_species", pet_name_species);
|
||||||
m.def("dog_bark", dog_bark);
|
m.def("dog_bark", dog_bark);
|
||||||
|
|
||||||
|
@ -2,7 +2,7 @@ import pytest
|
|||||||
|
|
||||||
|
|
||||||
def test_inheritance(msg):
|
def test_inheritance(msg):
|
||||||
from pybind11_tests import Pet, Dog, Rabbit, dog_bark, pet_name_species
|
from pybind11_tests import Pet, Dog, Rabbit, Hamster, dog_bark, pet_name_species
|
||||||
|
|
||||||
roger = Rabbit('Rabbit')
|
roger = Rabbit('Rabbit')
|
||||||
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
|
assert roger.name() + " is a " + roger.species() == "Rabbit is a parrot"
|
||||||
@ -16,6 +16,9 @@ def test_inheritance(msg):
|
|||||||
assert molly.name() + " is a " + molly.species() == "Molly is a dog"
|
assert molly.name() + " is a " + molly.species() == "Molly is a dog"
|
||||||
assert pet_name_species(molly) == "Molly is a dog"
|
assert pet_name_species(molly) == "Molly is a dog"
|
||||||
|
|
||||||
|
fred = Hamster('Fred')
|
||||||
|
assert fred.name() + " is a " + fred.species() == "Fred is a rodent"
|
||||||
|
|
||||||
assert dog_bark(molly) == "Woof!"
|
assert dog_bark(molly) == "Woof!"
|
||||||
|
|
||||||
with pytest.raises(TypeError) as excinfo:
|
with pytest.raises(TypeError) as excinfo:
|
||||||
|
@ -96,7 +96,7 @@ void init_issues(py::module &m) {
|
|||||||
|
|
||||||
py::class_<ElementBase, std::shared_ptr<ElementBase>> (m2, "ElementBase");
|
py::class_<ElementBase, std::shared_ptr<ElementBase>> (m2, "ElementBase");
|
||||||
|
|
||||||
py::class_<ElementA, std::shared_ptr<ElementA>>(m2, "ElementA", py::base<ElementBase>())
|
py::class_<ElementA, ElementBase, std::shared_ptr<ElementA>>(m2, "ElementA")
|
||||||
.def(py::init<int>())
|
.def(py::init<int>())
|
||||||
.def("value", &ElementA::value);
|
.def("value", &ElementA::value);
|
||||||
|
|
||||||
|
@ -258,12 +258,12 @@ void initialize_inherited_virtuals(py::module &m) {
|
|||||||
.def("unlucky_number", &A_Repeat::unlucky_number)
|
.def("unlucky_number", &A_Repeat::unlucky_number)
|
||||||
.def("say_something", &A_Repeat::say_something)
|
.def("say_something", &A_Repeat::say_something)
|
||||||
.def("say_everything", &A_Repeat::say_everything);
|
.def("say_everything", &A_Repeat::say_everything);
|
||||||
py::class_<B_Repeat, PyB_Repeat>(m, "B_Repeat", py::base<A_Repeat>())
|
py::class_<B_Repeat, A_Repeat, PyB_Repeat>(m, "B_Repeat")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def("lucky_number", &B_Repeat::lucky_number);
|
.def("lucky_number", &B_Repeat::lucky_number);
|
||||||
py::class_<C_Repeat, PyC_Repeat>(m, "C_Repeat", py::base<B_Repeat>())
|
py::class_<C_Repeat, B_Repeat, PyC_Repeat>(m, "C_Repeat")
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
py::class_<D_Repeat, PyD_Repeat>(m, "D_Repeat", py::base<C_Repeat>())
|
py::class_<D_Repeat, C_Repeat, PyD_Repeat>(m, "D_Repeat")
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
|
|
||||||
// Method 2: Templated trampolines
|
// Method 2: Templated trampolines
|
||||||
@ -272,12 +272,12 @@ void initialize_inherited_virtuals(py::module &m) {
|
|||||||
.def("unlucky_number", &A_Tpl::unlucky_number)
|
.def("unlucky_number", &A_Tpl::unlucky_number)
|
||||||
.def("say_something", &A_Tpl::say_something)
|
.def("say_something", &A_Tpl::say_something)
|
||||||
.def("say_everything", &A_Tpl::say_everything);
|
.def("say_everything", &A_Tpl::say_everything);
|
||||||
py::class_<B_Tpl, PyB_Tpl<>>(m, "B_Tpl", py::base<A_Tpl>())
|
py::class_<B_Tpl, A_Tpl, PyB_Tpl<>>(m, "B_Tpl")
|
||||||
.def(py::init<>())
|
.def(py::init<>())
|
||||||
.def("lucky_number", &B_Tpl::lucky_number);
|
.def("lucky_number", &B_Tpl::lucky_number);
|
||||||
py::class_<C_Tpl, PyB_Tpl<C_Tpl>>(m, "C_Tpl", py::base<B_Tpl>())
|
py::class_<C_Tpl, B_Tpl, PyB_Tpl<C_Tpl>>(m, "C_Tpl")
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
py::class_<D_Tpl, PyB_Tpl<D_Tpl>>(m, "D_Tpl", py::base<C_Tpl>())
|
py::class_<D_Tpl, C_Tpl, PyB_Tpl<D_Tpl>>(m, "D_Tpl")
|
||||||
.def(py::init<>());
|
.def(py::init<>());
|
||||||
|
|
||||||
};
|
};
|
||||||
|
Loading…
Reference in New Issue
Block a user