From 5fffe200e337f1a1bb13034fac29a63ad6eae304 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Sep 2016 12:17:06 -0400 Subject: [PATCH 1/2] Allow arbitrary class_ template option ordering The current pybind11::class_ fixed template ordering results in a requirement to repeat the Holder with its default value (std::unique_ptr) argument, which is a little bit annoying: it needs to be specified not because we want to override the default, but rather because we need to specify the third argument. This commit removes this limitation by making the class_ template take the type name plus a parameter pack of options. It then extracts the first valid holder type and the first subclass type for holder_type and trampoline type_alias, respectively. (If unfound, both fall back to their current defaults, `std::unique_ptr` and `type`, respectively). If any unmatched template arguments are provided, a static assertion fails. What this means is that you can specify or omit the arguments in any order: py::class_ c1(m, "A"); py::class_> c2(m, "B"); py::class_, PyB> c3(m, "C"); It also allows future class attributes (such as base types in the next commit) to be passed as class template types rather than needing to use a py::base<> wrapper. --- docs/advanced.rst | 31 +++++++------- include/pybind11/cast.h | 7 ++++ include/pybind11/common.h | 22 ++++++++++ include/pybind11/operators.h | 10 +++-- include/pybind11/pybind11.h | 70 +++++++++++++++++++++----------- tests/CMakeLists.txt | 1 + tests/test_class_args.cpp | 69 +++++++++++++++++++++++++++++++ tests/test_class_args.py | 7 ++++ tests/test_issues.cpp | 2 +- tests/test_virtual_functions.cpp | 20 ++++----- 10 files changed, 187 insertions(+), 52 deletions(-) create mode 100644 tests/test_class_args.cpp create mode 100644 tests/test_class_args.py diff --git a/docs/advanced.rst b/docs/advanced.rst index cf588af3e..6a4a02077 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -311,7 +311,7 @@ The binding code also needs a few minor adaptations (highlighted): PYBIND11_PLUGIN(example) { py::module m("example", "pybind11 example plugin"); - py::class_, PyAnimal /* <--- trampoline*/> animal(m, "Animal"); + py::class_ animal(m, "Animal"); animal .def(py::init<>()) .def("go", &Animal::go); @@ -325,9 +325,10 @@ The binding code also needs a few minor adaptations (highlighted): } Importantly, pybind11 is made aware of the trampoline trampoline helper class -by specifying it as the *third* template argument to :class:`class_`. The -second argument with the unique pointer is simply the default holder type used -by pybind11. Following this, we are able to define a constructor as usual. +by specifying it as an extra template argument to :class:`class_`. (This can +also be combined with other template arguments such as a custom holder type; +the order of template types does not matter). Following this, we are able to +define a constructor as usual. Note, however, that the above is sufficient for allowing python classes to extend ``Animal``, but not ``Dog``: see ref:`virtual_and_inheritance` for the @@ -453,9 +454,9 @@ The classes are then registered with pybind11 using: .. code-block:: cpp - py::class_, PyAnimal<>> animal(m, "Animal"); - py::class_, PyDog<>> dog(m, "Dog"); - py::class_, PyDog> husky(m, "Husky"); + py::class_> animal(m, "Animal"); + py::class_> dog(m, "Dog"); + py::class_> husky(m, "Husky"); // ... add animal, dog, husky definitions Note that ``Husky`` did not require a dedicated trampoline template class at @@ -525,7 +526,7 @@ be realized as follows (important changes highlighted): PYBIND11_PLUGIN(example) { py::module m("example", "pybind11 example plugin"); - py::class_, PyAnimal> animal(m, "Animal"); + py::class_ animal(m, "Animal"); animal .def(py::init<>()) .def("go", &Animal::go); @@ -939,11 +940,11 @@ This section explains how to pass values that are wrapped in "smart" pointer types with internal reference counting. For the simpler C++11 unique pointers, refer to the previous section. -The binding generator for classes, :class:`class_`, takes an optional second -template type, which denotes a special *holder* type that is used to manage -references to the object. When wrapping a type named ``Type``, the default -value of this template parameter is ``std::unique_ptr``, which means that -the object is deallocated when Python's reference count goes to zero. +The binding generator for classes, :class:`class_`, can be passed a template +type that denotes a special *holder* type that is used to manage references to +the object. If no such holder type template argument is given, the default for +a type named ``Type`` is ``std::unique_ptr``, which means that the object +is deallocated when Python's reference count goes to zero. It is possible to switch to other types of reference counting wrappers or smart pointers, which is useful in codebases that rely on them. For instance, the @@ -977,6 +978,8 @@ code? .. code-block:: cpp + PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + class Child { }; class Parent { @@ -1089,7 +1092,7 @@ pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that is responsible for managing the lifetime of instances will reference the destructor even if no deallocations ever take place. In order to expose classes with private or protected destructors, it is possible to override the holder -type via the second argument to ``class_``. Pybind11 provides a helper class +type via a holder type argument to ``class_``. Pybind11 provides a helper class ``py::nodelete`` that disables any destructor invocations. In this case, it is crucial that instances are deallocated on the C++ side to avoid memory leaks. diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index c8c8f77ba..e5579e1b4 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -798,6 +798,13 @@ protected: holder_type holder; }; +template struct is_holder_type : + // PYBIND11_DECLARE_HOLDER_TYPE holder types: + std::conditional, detail::type_caster>::value, + std::true_type, + std::false_type>::type {}; +template struct is_holder_type> : std::true_type {}; + template struct handle_type_name { static PYBIND11_DESCR name() { return _(); } }; template <> struct handle_type_name { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } }; template <> struct handle_type_name { static PYBIND11_DESCR name() { return _("*args"); } }; diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 3d5aeb22a..6aeeb3d7a 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -358,6 +358,28 @@ template class P, typename T, typename... Ts> struct any_of_t : conditional_t::value, std::true_type, any_of_t> { }; #endif +// Extracts the first type from the template parameter pack matching the predicate, or void if none match. +template class Predicate, class... Ts> struct first_of; +template class Predicate> struct first_of { + using type = void; +}; +template class Predicate, class T, class... Ts> +struct first_of { + using type = typename std::conditional< + Predicate::value, + T, + typename first_of::type + >::type; +}; +template class Predicate, class... T> using first_of_t = typename first_of::type; + +// Counts the number of types in the template parameter pack matching the predicate +template class Predicate, typename... Ts> struct count_t; +template class Predicate> struct count_t : std::integral_constant {}; +template class Predicate, class T, class... Ts> +struct count_t : std::integral_constant::value + count_t::value> {}; + /// Defer the evaluation of type T until types Us are instantiated template struct deferred_type { using type = T; }; template using deferred_t = typename deferred_type::type; diff --git a/include/pybind11/operators.h b/include/pybind11/operators.h index 39f9786a7..eda51a178 100644 --- a/include/pybind11/operators.h +++ b/include/pybind11/operators.h @@ -49,17 +49,19 @@ template struct op_impl { } /// Operator implementation generator template struct op_ { - template void execute(pybind11::class_ &class_, const Extra&... extra) const { + template void execute(Class &cl, const Extra&... extra) const { + typedef typename Class::type Base; typedef typename std::conditional::value, Base, L>::type L_type; typedef typename std::conditional::value, Base, R>::type R_type; typedef op_impl op; - class_.def(op::name(), &op::execute, extra...); + cl.def(op::name(), &op::execute, extra...); } - template void execute_cast(pybind11::class_ &class_, const Extra&... extra) const { + template void execute_cast(Class &cl, const Extra&... extra) const { + typedef typename Class::type Base; typedef typename std::conditional::value, Base, L>::type L_type; typedef typename std::conditional::value, Base, R>::type R_type; typedef op_impl op; - class_.def(op::name(), &op::execute_cast, extra...); + cl.def(op::name(), &op::execute_cast, extra...); } }; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 2104671db..3f04ba54a 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -563,7 +563,7 @@ public: NAMESPACE_BEGIN(detail) /// Generic support for creating new Python heap types class generic_type : public object { - template friend class class_; + template friend class class_; public: PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check) protected: @@ -804,10 +804,31 @@ protected: }; NAMESPACE_END(detail) -template , typename type_alias = type> +template class class_ : public detail::generic_type { + template using is_holder = detail::is_holder_type; + template using is_subtype = detail::bool_constant::value && !std::is_same::value>; + template using is_valid_class_option = + detail::bool_constant< + is_holder::value || + is_subtype::value + >; + + using extracted_holder_t = typename detail::first_of_t; + public: - typedef detail::instance instance_type; + using type = type_; + using type_alias = detail::first_of_t; + constexpr static bool has_alias = !std::is_void::value; + using holder_type = typename std::conditional< + std::is_void::value, + std::unique_ptr, + extracted_holder_t + >::type; + using instance_type = detail::instance; + + static_assert(detail::all_of_t::value, + "Unknown/invalid class_ template parameters provided"); PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check) @@ -827,7 +848,7 @@ public: detail::generic_type::initialize(&record); - if (!std::is_same::value) { + if (has_alias) { auto &instances = pybind11::detail::get_internals().registered_types_cpp; instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))]; } @@ -852,25 +873,25 @@ public: template class_ &def(const detail::op_ &op, const Extra&... extra) { - op.template execute(*this, extra...); + op.execute(*this, extra...); return *this; } template class_ & def_cast(const detail::op_ &op, const Extra&... extra) { - op.template execute_cast(*this, extra...); + op.execute_cast(*this, extra...); return *this; } template class_ &def(const detail::init &init, const Extra&... extra) { - init.template execute(*this, extra...); + init.execute(*this, extra...); return *this; } template class_ &def(const detail::init_alias &init, const Extra&... extra) { - init.template execute(*this, extra...); + init.execute(*this, extra...); return *this; } @@ -1071,19 +1092,21 @@ private: NAMESPACE_BEGIN(detail) template struct init { - template ::value, int>::type = 0> - void execute(pybind11::class_ &class_, const Extra&... extra) const { + template ::type = 0> + void execute(Class &cl, const Extra&... extra) const { + using Base = typename Class::type; /// Function which calls a specific C++ in-place constructor - class_.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...); + cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...); } - template ::value && - std::is_constructible::value, int>::type = 0> - void execute(pybind11::class_ &class_, const Extra&... extra) const { - handle cl_type = class_; - class_.def("__init__", [cl_type](handle self_, Args... args) { + template ::value, int>::type = 0> + void execute(Class &cl, const Extra&... extra) const { + using Base = typename Class::type; + using Alias = typename Class::type_alias; + handle cl_type = cl; + cl.def("__init__", [cl_type](handle self_, Args... args) { if (self_.get_type() == cl_type) new (self_.cast()) Base(args...); else @@ -1091,11 +1114,12 @@ template struct init { }, extra...); } - template ::value && - !std::is_constructible::value, int>::type = 0> - void execute(pybind11::class_ &class_, const Extra&... extra) const { - class_.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...); + template ::value, int>::type = 0> + void execute(Class &cl, const Extra&... extra) const { + using Alias = typename Class::type_alias; + cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...); } }; diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 0efefaae3..6d72ad7ed 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -6,6 +6,7 @@ endif() set(PYBIND11_TEST_FILES test_buffers.cpp test_callbacks.cpp + test_class_args.cpp test_constants_and_functions.cpp test_eigen.cpp test_enum.cpp diff --git a/tests/test_class_args.cpp b/tests/test_class_args.cpp new file mode 100644 index 000000000..c1207016c --- /dev/null +++ b/tests/test_class_args.cpp @@ -0,0 +1,69 @@ +/* + tests/test_class_args.cpp -- tests that various way of defining a class work + + Copyright (c) 2016 Wenzel Jakob + + All rights reserved. Use of this source code is governed by a + BSD-style license that can be found in the LICENSE file. +*/ + +#include "pybind11_tests.h" + +PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr); + +template class BreaksBase {}; +template class BreaksTramp : public BreaksBase {}; +// These should all compile just fine: +typedef py::class_, std::unique_ptr>, BreaksTramp<1>> DoesntBreak1; +typedef py::class_, BreaksTramp<2>, std::unique_ptr>> DoesntBreak2; +typedef py::class_, std::unique_ptr>> DoesntBreak3; +typedef py::class_, BreaksTramp<4>> DoesntBreak4; +typedef py::class_> DoesntBreak5; +typedef py::class_, std::shared_ptr>, BreaksTramp<6>> DoesntBreak6; +typedef py::class_, BreaksTramp<7>, std::shared_ptr>> DoesntBreak7; +typedef py::class_, std::shared_ptr>> DoesntBreak8; +#define CHECK_BASE(N) static_assert(std::is_same>::value, \ + "DoesntBreak" #N " has wrong type!") +CHECK_BASE(1); CHECK_BASE(2); CHECK_BASE(3); CHECK_BASE(4); CHECK_BASE(5); CHECK_BASE(6); CHECK_BASE(7); CHECK_BASE(8); +#define CHECK_ALIAS(N) static_assert(DoesntBreak##N::has_alias && std::is_same>::value, \ + "DoesntBreak" #N " has wrong type_alias!") +#define CHECK_NOALIAS(N) static_assert(!DoesntBreak##N::has_alias && std::is_void::value, \ + "DoesntBreak" #N " has type alias, but shouldn't!") +CHECK_ALIAS(1); CHECK_ALIAS(2); CHECK_NOALIAS(3); CHECK_ALIAS(4); CHECK_NOALIAS(5); CHECK_ALIAS(6); CHECK_ALIAS(7); CHECK_NOALIAS(8); +#define CHECK_HOLDER(N, TYPE) static_assert(std::is_same>>::value, \ + "DoesntBreak" #N " has wrong holder_type!") +CHECK_HOLDER(1, unique); CHECK_HOLDER(2, unique); CHECK_HOLDER(3, unique); CHECK_HOLDER(4, unique); CHECK_HOLDER(5, unique); +CHECK_HOLDER(6, shared); CHECK_HOLDER(7, shared); CHECK_HOLDER(8, shared); + +// There's no nice way to test that these fail because they fail to compile; leave them here, +// though, so that they can be manually tested by uncommenting them (and seeing that compilation +// failures occurs). + +// We have to actually look into the type: the typedef alone isn't enough to instantiate the type: +#define CHECK_BROKEN(N) static_assert(std::is_same>::value, \ + "Breaks1 has wrong type!"); + +//// Two holder classes: +//typedef py::class_, std::unique_ptr>, std::unique_ptr>> Breaks1; +//CHECK_BROKEN(1); +//// Two aliases: +//typedef py::class_, BreaksTramp<-2>, BreaksTramp<-2>> Breaks2; +//CHECK_BROKEN(2); +//// Holder + 2 aliases +//typedef py::class_, std::unique_ptr>, BreaksTramp<-3>, BreaksTramp<-3>> Breaks3; +//CHECK_BROKEN(3); +//// Alias + 2 holders +//typedef py::class_, std::unique_ptr>, BreaksTramp<-4>, std::shared_ptr>> Breaks4; +//CHECK_BROKEN(4); +//// Invalid option (not a subclass or holder) +//typedef py::class_, BreaksTramp<-4>> Breaks5; +//CHECK_BROKEN(5); +//// Invalid option: multiple inheritance not supported: +//template <> struct BreaksBase<-8> : BreaksBase<-6>, BreaksBase<-7> {}; +//typedef py::class_, BreaksBase<-6>, BreaksBase<-7>> Breaks8; +//CHECK_BROKEN(8); + +test_initializer class_args([](py::module &m) { + // Just test that this compiled okay + m.def("class_args_noop", []() {}); +}); diff --git a/tests/test_class_args.py b/tests/test_class_args.py new file mode 100644 index 000000000..a1fc21a7d --- /dev/null +++ b/tests/test_class_args.py @@ -0,0 +1,7 @@ + +import pytest + +def test_class_args(): + # There's basically nothing to test here; just make sure the code compiled and declared its definition + from pybind11_tests import class_args_noop + class_args_noop() diff --git a/tests/test_issues.cpp b/tests/test_issues.cpp index 16a44c74f..0a5a0b09a 100644 --- a/tests/test_issues.cpp +++ b/tests/test_issues.cpp @@ -51,7 +51,7 @@ void init_issues(py::module &m) { } }; - py::class_, DispatchIssue>(m2, "DispatchIssue") + py::class_(m2, "DispatchIssue") .def(py::init<>()) .def("dispatch", &Base::dispatch); diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index e591ba922..ac5b3fbbd 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -253,31 +253,31 @@ public: void initialize_inherited_virtuals(py::module &m) { // Method 1: repeat - py::class_, PyA_Repeat>(m, "A_Repeat") + py::class_(m, "A_Repeat") .def(py::init<>()) .def("unlucky_number", &A_Repeat::unlucky_number) .def("say_something", &A_Repeat::say_something) .def("say_everything", &A_Repeat::say_everything); - py::class_, PyB_Repeat>(m, "B_Repeat", py::base()) + py::class_(m, "B_Repeat", py::base()) .def(py::init<>()) .def("lucky_number", &B_Repeat::lucky_number); - py::class_, PyC_Repeat>(m, "C_Repeat", py::base()) + py::class_(m, "C_Repeat", py::base()) .def(py::init<>()); - py::class_, PyD_Repeat>(m, "D_Repeat", py::base()) + py::class_(m, "D_Repeat", py::base()) .def(py::init<>()); // Method 2: Templated trampolines - py::class_, PyA_Tpl<>>(m, "A_Tpl") + py::class_>(m, "A_Tpl") .def(py::init<>()) .def("unlucky_number", &A_Tpl::unlucky_number) .def("say_something", &A_Tpl::say_something) .def("say_everything", &A_Tpl::say_everything); - py::class_, PyB_Tpl<>>(m, "B_Tpl", py::base()) + py::class_>(m, "B_Tpl", py::base()) .def(py::init<>()) .def("lucky_number", &B_Tpl::lucky_number); - py::class_, PyB_Tpl>(m, "C_Tpl", py::base()) + py::class_>(m, "C_Tpl", py::base()) .def(py::init<>()); - py::class_, PyB_Tpl>(m, "D_Tpl", py::base()) + py::class_>(m, "D_Tpl", py::base()) .def(py::init<>()); }; @@ -287,7 +287,7 @@ test_initializer virtual_functions([](py::module &m) { /* Important: indicate the trampoline class PyExampleVirt using the third argument to py::class_. The second argument with the unique pointer is simply the default holder type used by pybind11. */ - py::class_, PyExampleVirt>(m, "ExampleVirt") + py::class_(m, "ExampleVirt") .def(py::init()) /* Reference original class in function definitions */ .def("run", &ExampleVirt::run) @@ -301,7 +301,7 @@ test_initializer virtual_functions([](py::module &m) { .def(py::init()); #if !defined(__INTEL_COMPILER) - py::class_, NCVirtTrampoline>(m, "NCVirt") + py::class_(m, "NCVirt") .def(py::init<>()) .def("get_noncopyable", &NCVirt::get_noncopyable) .def("get_movable", &NCVirt::get_movable) From 6b52c838d78cb697f68761c75b82d2a1806aedde Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Tue, 6 Sep 2016 12:27:00 -0400 Subject: [PATCH 2/2] Allow passing base types as a template parameter This allows a slightly cleaner base type specification of: py::class_("Type") as an alternative to py::class_("Type", py::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). --- docs/advanced.rst | 13 +++++++------ docs/classes.rst | 16 ++++++++++++---- include/pybind11/cast.h | 10 +++++----- include/pybind11/common.h | 16 ++++++++-------- include/pybind11/pybind11.h | 33 +++++++++++++++++++++++--------- tests/test_inheritance.cpp | 9 +++++++++ tests/test_inheritance.py | 5 ++++- tests/test_issues.cpp | 2 +- tests/test_virtual_functions.cpp | 12 ++++++------ 9 files changed, 76 insertions(+), 40 deletions(-) diff --git a/docs/advanced.rst b/docs/advanced.rst index 6a4a02077..748f91e2e 100644 --- a/docs/advanced.rst +++ b/docs/advanced.rst @@ -1227,7 +1227,7 @@ section. the other existing exception translators. The ``py::exception`` wrapper for creating custom exceptions cannot (yet) - be used as a ``py::base``. + be used as a base type. .. _eigen: @@ -1811,16 +1811,17 @@ However, it can be acquired as follows: .def(py::init()) .def("bark", &Dog::bark); -Alternatively, we can rely on the ``base`` tag, which performs an automated -lookup of the corresponding Python type. However, this also requires invoking -the ``import`` function once to ensure that the pybind11 binding code of the -module ``basic`` has been executed. +Alternatively, you can specify the base class as a template parameter option to +``class_``, which performs an automated lookup of the corresponding Python +type. Like the above code, however, this also requires invoking the ``import`` +function once to ensure that the pybind11 binding code of the module ``basic`` +has been executed: .. code-block:: cpp py::module::import("basic"); - py::class_(m, "Dog", py::base()) + py::class_(m, "Dog") .def(py::init()) .def("bark", &Dog::bark); diff --git a/docs/classes.rst b/docs/classes.rst index 5afb21edc..80f378f68 100644 --- a/docs/classes.rst +++ b/docs/classes.rst @@ -185,9 +185,10 @@ inheritance relationship: std::string bark() const { return "woof!"; } }; -There are two different ways of indicating a hierarchical relationship to -pybind11: the first is by specifying the C++ base class explicitly during -construction using the ``base`` attribute: +There are three different ways of indicating a hierarchical relationship to +pybind11: the first specifies the C++ base class as an extra template +parameter of the :class:`class_`; the second uses a special ``base`` attribute +passed into the constructor: .. code-block:: cpp @@ -195,6 +196,12 @@ construction using the ``base`` attribute: .def(py::init()) .def_readwrite("name", &Pet::name); + // Method 1: template parameter: + py::class_(m, "Dog") + .def(py::init()) + .def("bark", &Dog::bark); + + // Method 2: py::base attribute: py::class_(m, "Dog", py::base() /* <- specify C++ parent type */) .def(py::init()) .def("bark", &Dog::bark); @@ -208,11 +215,12 @@ Alternatively, we can also assign a name to the previously bound ``Pet`` pet.def(py::init()) .def_readwrite("name", &Pet::name); + // Method 3: pass parent class_ object: py::class_(m, "Dog", pet /* <- specify Python parent type */) .def(py::init()) .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: .. code-block:: pycon diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h index e5579e1b4..f6d86e3cf 100644 --- a/include/pybind11/cast.h +++ b/include/pybind11/cast.h @@ -798,12 +798,12 @@ protected: holder_type holder; }; +// PYBIND11_DECLARE_HOLDER_TYPE holder types: template struct is_holder_type : - // PYBIND11_DECLARE_HOLDER_TYPE holder types: - std::conditional, detail::type_caster>::value, - std::true_type, - std::false_type>::type {}; -template struct is_holder_type> : std::true_type {}; + std::is_base_of, detail::type_caster> {}; +// Specialization for always-supported unique_ptr holders: +template struct is_holder_type> : + std::true_type {}; template struct handle_type_name { static PYBIND11_DESCR name() { return _(); } }; template <> struct handle_type_name { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } }; diff --git a/include/pybind11/common.h b/include/pybind11/common.h index 6aeeb3d7a..c086e8796 100644 --- a/include/pybind11/common.h +++ b/include/pybind11/common.h @@ -358,20 +358,20 @@ template class P, typename T, typename... Ts> struct any_of_t : conditional_t::value, std::true_type, any_of_t> { }; #endif -// Extracts the first type from the template parameter pack matching the predicate, or void if none match. -template class Predicate, class... Ts> struct first_of; -template class Predicate> struct first_of { - using type = void; +// Extracts the first type from the template parameter pack matching the predicate, or Default if none match. +template class Predicate, class Default, class... Ts> struct first_of; +template class Predicate, class Default> struct first_of { + using type = Default; }; -template class Predicate, class T, class... Ts> -struct first_of { +template class Predicate, class Default, class T, class... Ts> +struct first_of { using type = typename std::conditional< Predicate::value, T, - typename first_of::type + typename first_of::type >::type; }; -template class Predicate, class... T> using first_of_t = typename first_of::type; +template class Predicate, class Default, class... T> using first_of_t = typename first_of::type; // Counts the number of types in the template parameter pack matching the predicate template class Predicate, typename... Ts> struct count_t; diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 3f04ba54a..0d16c7a1f 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -802,34 +802,47 @@ protected: static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; } }; + +template class Predicate, typename... BaseTypes> struct class_selector; +template class Predicate, typename Base, typename... Bases> +struct class_selector { + static inline void set_bases(detail::type_record &record) { + if (Predicate::value) record.base_type = &typeid(Base); + else class_selector::set_bases(record); + } +}; +template class Predicate> +struct class_selector { + static inline void set_bases(detail::type_record &) {} +}; + NAMESPACE_END(detail) template class class_ : public detail::generic_type { template using is_holder = detail::is_holder_type; template using is_subtype = detail::bool_constant::value && !std::is_same::value>; + template using is_base_class = detail::bool_constant::value && !std::is_same::value>; template using is_valid_class_option = detail::bool_constant< is_holder::value || - is_subtype::value + is_subtype::value || + is_base_class::value >; - using extracted_holder_t = typename detail::first_of_t; - public: using type = type_; - using type_alias = detail::first_of_t; + using type_alias = detail::first_of_t; constexpr static bool has_alias = !std::is_void::value; - using holder_type = typename std::conditional< - std::is_void::value, - std::unique_ptr, - extracted_holder_t - >::type; + using holder_type = detail::first_of_t, options...>; using instance_type = detail::instance; static_assert(detail::all_of_t::value, "Unknown/invalid class_ template parameters provided"); + static_assert(detail::count_t::value <= 1, + "Invalid class_ base types: multiple inheritance is not supported"); + PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check) template @@ -843,6 +856,8 @@ public: record.init_holder = init_holder; record.dealloc = dealloc; + detail::class_selector::set_bases(record); + /* Process optional arguments, if any */ detail::process_attributes::init(extra..., &record); diff --git a/tests/test_inheritance.cpp b/tests/test_inheritance.cpp index e1aad9920..798befffc 100644 --- a/tests/test_inheritance.cpp +++ b/tests/test_inheritance.cpp @@ -31,6 +31,11 @@ public: 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) { return pet.name() + " is a " + pet.species(); } @@ -59,6 +64,10 @@ test_initializer inheritance([](py::module &m) { py::class_(m, "Rabbit", py::base()) .def(py::init()); + /* And another: list parent in class template arguments */ + py::class_(m, "Hamster") + .def(py::init()); + m.def("pet_name_species", pet_name_species); m.def("dog_bark", dog_bark); diff --git a/tests/test_inheritance.py b/tests/test_inheritance.py index b55490cf6..d4cea8253 100644 --- a/tests/test_inheritance.py +++ b/tests/test_inheritance.py @@ -2,7 +2,7 @@ import pytest 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') 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 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!" with pytest.raises(TypeError) as excinfo: diff --git a/tests/test_issues.cpp b/tests/test_issues.cpp index 0a5a0b09a..55502fe18 100644 --- a/tests/test_issues.cpp +++ b/tests/test_issues.cpp @@ -96,7 +96,7 @@ void init_issues(py::module &m) { py::class_> (m2, "ElementBase"); - py::class_>(m2, "ElementA", py::base()) + py::class_>(m2, "ElementA") .def(py::init()) .def("value", &ElementA::value); diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp index ac5b3fbbd..381b87e0e 100644 --- a/tests/test_virtual_functions.cpp +++ b/tests/test_virtual_functions.cpp @@ -258,12 +258,12 @@ void initialize_inherited_virtuals(py::module &m) { .def("unlucky_number", &A_Repeat::unlucky_number) .def("say_something", &A_Repeat::say_something) .def("say_everything", &A_Repeat::say_everything); - py::class_(m, "B_Repeat", py::base()) + py::class_(m, "B_Repeat") .def(py::init<>()) .def("lucky_number", &B_Repeat::lucky_number); - py::class_(m, "C_Repeat", py::base()) + py::class_(m, "C_Repeat") .def(py::init<>()); - py::class_(m, "D_Repeat", py::base()) + py::class_(m, "D_Repeat") .def(py::init<>()); // Method 2: Templated trampolines @@ -272,12 +272,12 @@ void initialize_inherited_virtuals(py::module &m) { .def("unlucky_number", &A_Tpl::unlucky_number) .def("say_something", &A_Tpl::say_something) .def("say_everything", &A_Tpl::say_everything); - py::class_>(m, "B_Tpl", py::base()) + py::class_>(m, "B_Tpl") .def(py::init<>()) .def("lucky_number", &B_Tpl::lucky_number); - py::class_>(m, "C_Tpl", py::base()) + py::class_>(m, "C_Tpl") .def(py::init<>()); - py::class_>(m, "D_Tpl", py::base()) + py::class_>(m, "D_Tpl") .def(py::init<>()); };