Skip to content

Commit 6fd3132

Browse files
authored
Merge pull request #385 from jagerman/relax-class-arguments
Allow arbitrary class_ template option ordering
2 parents 837fda2 + 6b52c83 commit 6fd3132

13 files changed

+235
-64
lines changed

docs/advanced.rst

Lines changed: 24 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -311,7 +311,7 @@ The binding code also needs a few minor adaptations (highlighted):
311311
PYBIND11_PLUGIN(example) {
312312
py::module m("example", "pybind11 example plugin");
313313
314-
py::class_<Animal, std::unique_ptr<Animal>, PyAnimal /* <--- trampoline*/> animal(m, "Animal");
314+
py::class_<Animal, PyAnimal /* <--- trampoline*/> animal(m, "Animal");
315315
animal
316316
.def(py::init<>())
317317
.def("go", &Animal::go);
@@ -325,9 +325,10 @@ The binding code also needs a few minor adaptations (highlighted):
325325
}
326326
327327
Importantly, pybind11 is made aware of the trampoline trampoline helper class
328-
by specifying it as the *third* template argument to :class:`class_`. The
329-
second argument with the unique pointer is simply the default holder type used
330-
by pybind11. Following this, we are able to define a constructor as usual.
328+
by specifying it as an extra template argument to :class:`class_`. (This can
329+
also be combined with other template arguments such as a custom holder type;
330+
the order of template types does not matter). Following this, we are able to
331+
define a constructor as usual.
331332

332333
Note, however, that the above is sufficient for allowing python classes to
333334
extend ``Animal``, but not ``Dog``: see ref:`virtual_and_inheritance` for the
@@ -453,9 +454,9 @@ The classes are then registered with pybind11 using:
453454

454455
.. code-block:: cpp
455456
456-
py::class_<Animal, std::unique_ptr<Animal>, PyAnimal<>> animal(m, "Animal");
457-
py::class_<Dog, std::unique_ptr<Dog>, PyDog<>> dog(m, "Dog");
458-
py::class_<Husky, std::unique_ptr<Husky>, PyDog<Husky>> husky(m, "Husky");
457+
py::class_<Animal, PyAnimal<>> animal(m, "Animal");
458+
py::class_<Dog, PyDog<>> dog(m, "Dog");
459+
py::class_<Husky, PyDog<Husky>> husky(m, "Husky");
459460
// ... add animal, dog, husky definitions
460461
461462
Note that ``Husky`` did not require a dedicated trampoline template class at
@@ -525,7 +526,7 @@ be realized as follows (important changes highlighted):
525526
PYBIND11_PLUGIN(example) {
526527
py::module m("example", "pybind11 example plugin");
527528
528-
py::class_<Animal, std::unique_ptr<Animal>, PyAnimal> animal(m, "Animal");
529+
py::class_<Animal, PyAnimal> animal(m, "Animal");
529530
animal
530531
.def(py::init<>())
531532
.def("go", &Animal::go);
@@ -939,11 +940,11 @@ This section explains how to pass values that are wrapped in "smart" pointer
939940
types with internal reference counting. For the simpler C++11 unique pointers,
940941
refer to the previous section.
941942

942-
The binding generator for classes, :class:`class_`, takes an optional second
943-
template type, which denotes a special *holder* type that is used to manage
944-
references to the object. When wrapping a type named ``Type``, the default
945-
value of this template parameter is ``std::unique_ptr<Type>``, which means that
946-
the object is deallocated when Python's reference count goes to zero.
943+
The binding generator for classes, :class:`class_`, can be passed a template
944+
type that denotes a special *holder* type that is used to manage references to
945+
the object. If no such holder type template argument is given, the default for
946+
a type named ``Type`` is ``std::unique_ptr<Type>``, which means that the object
947+
is deallocated when Python's reference count goes to zero.
947948

948949
It is possible to switch to other types of reference counting wrappers or smart
949950
pointers, which is useful in codebases that rely on them. For instance, the
@@ -977,6 +978,8 @@ code?
977978

978979
.. code-block:: cpp
979980
981+
PYBIND11_DECLARE_HOLDER_TYPE(T, std::shared_ptr<T>);
982+
980983
class Child { };
981984
982985
class Parent {
@@ -1089,7 +1092,7 @@ pybind11. The underlying issue is that the ``std::unique_ptr`` holder type that
10891092
is responsible for managing the lifetime of instances will reference the
10901093
destructor even if no deallocations ever take place. In order to expose classes
10911094
with private or protected destructors, it is possible to override the holder
1092-
type via the second argument to ``class_``. Pybind11 provides a helper class
1095+
type via a holder type argument to ``class_``. Pybind11 provides a helper class
10931096
``py::nodelete`` that disables any destructor invocations. In this case, it is
10941097
crucial that instances are deallocated on the C++ side to avoid memory leaks.
10951098

@@ -1224,7 +1227,7 @@ section.
12241227
the other existing exception translators.
12251228

12261229
The ``py::exception`` wrapper for creating custom exceptions cannot (yet)
1227-
be used as a ``py::base``.
1230+
be used as a base type.
12281231

12291232
.. _eigen:
12301233

@@ -1808,16 +1811,17 @@ However, it can be acquired as follows:
18081811
.def(py::init<const std::string &>())
18091812
.def("bark", &Dog::bark);
18101813
1811-
Alternatively, we can rely on the ``base`` tag, which performs an automated
1812-
lookup of the corresponding Python type. However, this also requires invoking
1813-
the ``import`` function once to ensure that the pybind11 binding code of the
1814-
module ``basic`` has been executed.
1814+
Alternatively, you can specify the base class as a template parameter option to
1815+
``class_``, which performs an automated lookup of the corresponding Python
1816+
type. Like the above code, however, this also requires invoking the ``import``
1817+
function once to ensure that the pybind11 binding code of the module ``basic``
1818+
has been executed:
18151819

18161820
.. code-block:: cpp
18171821
18181822
py::module::import("basic");
18191823
1820-
py::class_<Dog>(m, "Dog", py::base<Pet>())
1824+
py::class_<Dog, Pet>(m, "Dog")
18211825
.def(py::init<const std::string &>())
18221826
.def("bark", &Dog::bark);
18231827

docs/classes.rst

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,16 +185,23 @@ inheritance relationship:
185185
std::string bark() const { return "woof!"; }
186186
};
187187
188-
There are two different ways of indicating a hierarchical relationship to
189-
pybind11: the first is by specifying the C++ base class explicitly during
190-
construction using the ``base`` attribute:
188+
There are three different ways of indicating a hierarchical relationship to
189+
pybind11: the first specifies the C++ base class as an extra template
190+
parameter of the :class:`class_`; the second uses a special ``base`` attribute
191+
passed into the constructor:
191192

192193
.. code-block:: cpp
193194
194195
py::class_<Pet>(m, "Pet")
195196
.def(py::init<const std::string &>())
196197
.def_readwrite("name", &Pet::name);
197198
199+
// Method 1: template parameter:
200+
py::class_<Dog, Pet /* <- specify C++ parent type */>(m, "Dog")
201+
.def(py::init<const std::string &>())
202+
.def("bark", &Dog::bark);
203+
204+
// Method 2: py::base attribute:
198205
py::class_<Dog>(m, "Dog", py::base<Pet>() /* <- specify C++ parent type */)
199206
.def(py::init<const std::string &>())
200207
.def("bark", &Dog::bark);
@@ -208,11 +215,12 @@ Alternatively, we can also assign a name to the previously bound ``Pet``
208215
pet.def(py::init<const std::string &>())
209216
.def_readwrite("name", &Pet::name);
210217
218+
// Method 3: pass parent class_ object:
211219
py::class_<Dog>(m, "Dog", pet /* <- specify Python parent type */)
212220
.def(py::init<const std::string &>())
213221
.def("bark", &Dog::bark);
214222
215-
Functionality-wise, both approaches are completely equivalent. Afterwards,
223+
Functionality-wise, all three approaches are completely equivalent. Afterwards,
216224
instances will expose fields and methods of both types:
217225

218226
.. code-block:: pycon

include/pybind11/cast.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -800,6 +800,13 @@ template <typename type, typename holder_type> class type_caster_holder : public
800800
holder_type holder;
801801
};
802802

803+
// PYBIND11_DECLARE_HOLDER_TYPE holder types:
804+
template <typename base, typename holder> struct is_holder_type :
805+
std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>> {};
806+
// Specialization for always-supported unique_ptr holders:
807+
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> :
808+
std::true_type {};
809+
803810
template <typename T> struct handle_type_name { static PYBIND11_DESCR name() { return _<T>(); } };
804811
template <> struct handle_type_name<bytes> { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } };
805812
template <> struct handle_type_name<args> { static PYBIND11_DESCR name() { return _("*args"); } };

include/pybind11/common.h

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -358,6 +358,28 @@ template <template<typename> class P, typename T, typename... Ts>
358358
struct any_of_t<P, T, Ts...> : conditional_t<P<T>::value, std::true_type, any_of_t<P, Ts...>> { };
359359
#endif
360360

361+
// Extracts the first type from the template parameter pack matching the predicate, or Default if none match.
362+
template <template<class> class Predicate, class Default, class... Ts> struct first_of;
363+
template <template<class> class Predicate, class Default> struct first_of<Predicate, Default> {
364+
using type = Default;
365+
};
366+
template <template<class> class Predicate, class Default, class T, class... Ts>
367+
struct first_of<Predicate, Default, T, Ts...> {
368+
using type = typename std::conditional<
369+
Predicate<T>::value,
370+
T,
371+
typename first_of<Predicate, Default, Ts...>::type
372+
>::type;
373+
};
374+
template <template<class> class Predicate, class Default, class... T> using first_of_t = typename first_of<Predicate, Default, T...>::type;
375+
376+
// Counts the number of types in the template parameter pack matching the predicate
377+
template <template<typename> class Predicate, typename... Ts> struct count_t;
378+
template <template<typename> class Predicate> struct count_t<Predicate> : std::integral_constant<size_t, 0> {};
379+
template <template<typename> class Predicate, class T, class... Ts>
380+
struct count_t<Predicate, T, Ts...> : std::integral_constant<size_t,
381+
Predicate<T>::value + count_t<Predicate, Ts...>::value> {};
382+
361383
/// Defer the evaluation of type T until types Us are instantiated
362384
template <typename T, typename... /*Us*/> struct deferred_type { using type = T; };
363385
template <typename T, typename... Us> using deferred_t = typename deferred_type<T, Us...>::type;

include/pybind11/operators.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -49,17 +49,19 @@ template <op_id, op_type, typename B, typename L, typename R> struct op_impl { }
4949

5050
/// Operator implementation generator
5151
template <op_id id, op_type ot, typename L, typename R> struct op_ {
52-
template <typename Base, typename Holder, typename... Extra> void execute(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
52+
template <typename Class, typename... Extra> void execute(Class &cl, const Extra&... extra) const {
53+
typedef typename Class::type Base;
5354
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
5455
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
5556
typedef op_impl<id, ot, Base, L_type, R_type> op;
56-
class_.def(op::name(), &op::execute, extra...);
57+
cl.def(op::name(), &op::execute, extra...);
5758
}
58-
template <typename Base, typename Holder, typename... Extra> void execute_cast(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
59+
template <typename Class, typename... Extra> void execute_cast(Class &cl, const Extra&... extra) const {
60+
typedef typename Class::type Base;
5961
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
6062
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
6163
typedef op_impl<id, ot, Base, L_type, R_type> op;
62-
class_.def(op::name(), &op::execute_cast, extra...);
64+
cl.def(op::name(), &op::execute_cast, extra...);
6365
}
6466
};
6567

include/pybind11/pybind11.h

Lines changed: 62 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -563,7 +563,7 @@ class module : public object {
563563
NAMESPACE_BEGIN(detail)
564564
/// Generic support for creating new Python heap types
565565
class generic_type : public object {
566-
template <typename type, typename holder_type, typename type_alias> friend class class_;
566+
template <typename...> friend class class_;
567567
public:
568568
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
569569
protected:
@@ -802,12 +802,46 @@ class generic_type : public object {
802802

803803
static void releasebuffer(PyObject *, Py_buffer *view) { delete (buffer_info *) view->internal; }
804804
};
805+
806+
template <template<typename> class Predicate, typename... BaseTypes> struct class_selector;
807+
template <template<typename> class Predicate, typename Base, typename... Bases>
808+
struct class_selector<Predicate, Base, Bases...> {
809+
static inline void set_bases(detail::type_record &record) {
810+
if (Predicate<Base>::value) record.base_type = &typeid(Base);
811+
else class_selector<Predicate, Bases...>::set_bases(record);
812+
}
813+
};
814+
template <template<typename> class Predicate>
815+
struct class_selector<Predicate> {
816+
static inline void set_bases(detail::type_record &) {}
817+
};
818+
805819
NAMESPACE_END(detail)
806820

807-
template <typename type, typename holder_type = std::unique_ptr<type>, typename type_alias = type>
821+
template <typename type_, typename... options>
808822
class class_ : public detail::generic_type {
823+
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
824+
template <typename T> using is_subtype = detail::bool_constant<std::is_base_of<type_, T>::value && !std::is_same<T, type_>::value>;
825+
template <typename T> using is_base_class = detail::bool_constant<std::is_base_of<T, type_>::value && !std::is_same<T, type_>::value>;
826+
template <typename T> using is_valid_class_option =
827+
detail::bool_constant<
828+
is_holder<T>::value ||
829+
is_subtype<T>::value ||
830+
is_base_class<T>::value
831+
>;
832+
809833
public:
810-
typedef detail::instance<type, holder_type> instance_type;
834+
using type = type_;
835+
using type_alias = detail::first_of_t<is_subtype, void, options...>;
836+
constexpr static bool has_alias = !std::is_void<type_alias>::value;
837+
using holder_type = detail::first_of_t<is_holder, std::unique_ptr<type>, options...>;
838+
using instance_type = detail::instance<type, holder_type>;
839+
840+
static_assert(detail::all_of_t<is_valid_class_option, options...>::value,
841+
"Unknown/invalid class_ template parameters provided");
842+
843+
static_assert(detail::count_t<is_base_class, options...>::value <= 1,
844+
"Invalid class_ base types: multiple inheritance is not supported");
811845

812846
PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check)
813847

@@ -822,12 +856,14 @@ class class_ : public detail::generic_type {
822856
record.init_holder = init_holder;
823857
record.dealloc = dealloc;
824858

859+
detail::class_selector<is_base_class, options...>::set_bases(record);
860+
825861
/* Process optional arguments, if any */
826862
detail::process_attributes<Extra...>::init(extra..., &record);
827863

828864
detail::generic_type::initialize(&record);
829865

830-
if (!std::is_same<type, type_alias>::value) {
866+
if (has_alias) {
831867
auto &instances = pybind11::detail::get_internals().registered_types_cpp;
832868
instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))];
833869
}
@@ -852,25 +888,25 @@ class class_ : public detail::generic_type {
852888

853889
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
854890
class_ &def(const detail::op_<id, ot, L, R> &op, const Extra&... extra) {
855-
op.template execute<type>(*this, extra...);
891+
op.execute(*this, extra...);
856892
return *this;
857893
}
858894

859895
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
860896
class_ & def_cast(const detail::op_<id, ot, L, R> &op, const Extra&... extra) {
861-
op.template execute_cast<type>(*this, extra...);
897+
op.execute_cast(*this, extra...);
862898
return *this;
863899
}
864900

865901
template <typename... Args, typename... Extra>
866902
class_ &def(const detail::init<Args...> &init, const Extra&... extra) {
867-
init.template execute<type>(*this, extra...);
903+
init.execute(*this, extra...);
868904
return *this;
869905
}
870906

871907
template <typename... Args, typename... Extra>
872908
class_ &def(const detail::init_alias<Args...> &init, const Extra&... extra) {
873-
init.template execute<type>(*this, extra...);
909+
init.execute(*this, extra...);
874910
return *this;
875911
}
876912

@@ -1071,31 +1107,34 @@ template <typename Type> class enum_ : public class_<Type> {
10711107

10721108
NAMESPACE_BEGIN(detail)
10731109
template <typename... Args> struct init {
1074-
template <typename Base, typename Holder, typename Alias, typename... Extra,
1075-
typename std::enable_if<std::is_same<Base, Alias>::value, int>::type = 0>
1076-
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
1110+
template <typename Class, typename... Extra, typename std::enable_if<!Class::has_alias, int>::type = 0>
1111+
void execute(Class &cl, const Extra&... extra) const {
1112+
using Base = typename Class::type;
10771113
/// Function which calls a specific C++ in-place constructor
1078-
class_.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
1114+
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
10791115
}
10801116

1081-
template <typename Base, typename Holder, typename Alias, typename... Extra,
1082-
typename std::enable_if<!std::is_same<Base, Alias>::value &&
1083-
std::is_constructible<Base, Args...>::value, int>::type = 0>
1084-
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
1085-
handle cl_type = class_;
1086-
class_.def("__init__", [cl_type](handle self_, Args... args) {
1117+
template <typename Class, typename... Extra,
1118+
typename std::enable_if<Class::has_alias &&
1119+
std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
1120+
void execute(Class &cl, const Extra&... extra) const {
1121+
using Base = typename Class::type;
1122+
using Alias = typename Class::type_alias;
1123+
handle cl_type = cl;
1124+
cl.def("__init__", [cl_type](handle self_, Args... args) {
10871125
if (self_.get_type() == cl_type)
10881126
new (self_.cast<Base *>()) Base(args...);
10891127
else
10901128
new (self_.cast<Alias *>()) Alias(args...);
10911129
}, extra...);
10921130
}
10931131

1094-
template <typename Base, typename Holder, typename Alias, typename... Extra,
1095-
typename std::enable_if<!std::is_same<Base, Alias>::value &&
1096-
!std::is_constructible<Base, Args...>::value, int>::type = 0>
1097-
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
1098-
class_.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
1132+
template <typename Class, typename... Extra,
1133+
typename std::enable_if<Class::has_alias &&
1134+
!std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
1135+
void execute(Class &cl, const Extra&... extra) const {
1136+
using Alias = typename Class::type_alias;
1137+
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
10991138
}
11001139
};
11011140

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ endif()
66
set(PYBIND11_TEST_FILES
77
test_buffers.cpp
88
test_callbacks.cpp
9+
test_class_args.cpp
910
test_constants_and_functions.cpp
1011
test_eigen.cpp
1112
test_enum.cpp

0 commit comments

Comments
 (0)