Skip to content

Commit 30e9ef1

Browse files
committed
Allow binding factory functions as constructors
This allows you to use: cls.def(py::init(&factory_function)); where `factory_function` returns a pointer, holder, or value of the class type (or a derived type). Various compile-time checks (static_asserts) are performed to ensure the function is valid, and various run-time type checks where necessary. The feature is optional, and requires including the <pybind11/factory.h> header. Some other details of this feature: - The `py::init` name doesn't conflict with the templated no-argument `py::init<...>()`, but keeps the naming consistent (especially if we decide to roll this into the core, documented approach replacing raw placement-new `__init__`s). - If returning a CppClass (whether by value or pointer) when an CppAlias is required (i.e. python-side inheritance and a declared alias), a dynamic_cast to the alias is attempted (for the pointer version); if it fails, or if returned by value, an Alias(Class &&) constructor is invoked. If this constructor doesn't exist, a runtime error occurs. - for holder returns when an alias is required, we try a dynamic_cast of the wrapped pointer to the alias to see if it is already an alias instance; if it isn't, we raise an error. - `py::init(class_factory, alias_factory)` is also available that takes two factories: the first is called when an alias is not needed, the second when it is. - It also allows using a factory init to workaround binding a type without placement new support (issue pybind#948), which is basically impossible with this approach.
1 parent 10857a2 commit 30e9ef1

File tree

9 files changed

+961
-1
lines changed

9 files changed

+961
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ set(PYBIND11_HEADERS
4848
include/pybind11/eigen.h
4949
include/pybind11/embed.h
5050
include/pybind11/eval.h
51+
include/pybind11/factory.h
5152
include/pybind11/functional.h
5253
include/pybind11/numpy.h
5354
include/pybind11/operators.h

docs/advanced/classes.rst

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -322,6 +322,8 @@ can now create a python class that inherits from ``Dog``:
322322
See the file :file:`tests/test_virtual_functions.cpp` for complete examples
323323
using both the duplication and templated trampoline approaches.
324324

325+
.. _extended_aliases:
326+
325327
Extended trampoline class functionality
326328
=======================================
327329

@@ -382,6 +384,76 @@ In other words, :func:`init` creates an anonymous function that invokes an
382384
in-place constructor. Memory allocation etc. is already take care of beforehand
383385
within pybind11.
384386

387+
Factory function constructors
388+
=============================
389+
390+
When binding a C++ type that creates new instances through a factory function
391+
or static method, it is sometimes desirable to bind C++ factory function as a
392+
Python constructor rather than a Python factory function. This is possible
393+
when including the extra header ``pybind11/factory.h``:
394+
395+
.. code-block:: cpp
396+
397+
#include <pybind11/factory.h>
398+
class Example {
399+
// ...
400+
static Example *create(int a) { return new Example(a); }
401+
};
402+
py::class_<Example>(m, "Example")
403+
// Bind an existing pointer-returning factory function:
404+
.def(py::init(&Example::create))
405+
// Similar, but returns the pointer wrapped in a holder:
406+
.def(py::init([](std::string arg) {
407+
return std::unique_ptr<Example>(new Example(arg, "another arg"));
408+
}))
409+
// Can overload these with bound C++ constructors, too:
410+
.def(py::init<double>())
411+
;
412+
413+
When the constructor is invoked from Python, pybind11 will call the factory
414+
function and store the resulting C++ instance in the Python instance. Factory
415+
functions that return an object by value are also supported as long as the type
416+
is moveable or copyable.
417+
418+
When combining factory functions constructors with :ref:`_overriding_virtuals`
419+
there are two approaches. The first is to add a constructor to the alias class
420+
that takes a base value by rvalue-reference. If such a constructor is
421+
available, it will be used to construct an alias instance from the value
422+
returned by the factory function. The second option is to provide two factory
423+
functions to ``py::init()``: the first will be invoked when no alias class is
424+
required (i.e. when the class is being used but not inherited from in Python),
425+
and the second will be invoked when an alias is required.
426+
427+
You can also specify a single factory function that always returns an Alias
428+
instance: this will result in behaviour similar to ``py::init_alias<...>()``,
429+
as described in :ref:`_extended_aliases`.
430+
431+
The following example shows the different factory approaches for a class with an alias:
432+
433+
..code-block:: cpp
434+
435+
#include <pybind11/factory.h>
436+
class Example {
437+
public:
438+
// ...
439+
virtual ~Example() = default;
440+
};
441+
class PyExample : public Example {
442+
public:
443+
using Example::Example;
444+
PyExample(Example &&base) : Example(std::move(base)) {}
445+
};
446+
py::class_<Example, PyExample>(m, "Example")
447+
// Returns an Example pointer. If a PyExample is needed, it uses the
448+
// extra constructor added to PyExample, above.
449+
.def(py::init([]() { return new Example(); }))
450+
// Two callbacks: a no-alias-needed, and an alias-needed:
451+
.def(py::init([]() { return new Example(); },
452+
[]() { return new PyExample(); }))
453+
// Always return an alias instance, like py::init_alias<>()
454+
.def(py::init([]() { return new PyExample(); }))
455+
;
456+
385457
.. _classes_with_non_public_destructors:
386458

387459
Non-public destructors

include/pybind11/attr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ struct undefined_t;
118118
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
119119
template <typename... Args> struct init;
120120
template <typename... Args> struct init_alias;
121+
template <typename, typename, typename, typename, typename...> struct init_factory;
121122
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
122123

123124
/// Internal data structure which holds metadata about a keyword argument

include/pybind11/factory.h

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
/*
2+
pybind11/factory.h: Helper class for binding C++ factory functions
3+
as Python constructors.
4+
5+
Copyright (c) 2017 Jason Rhinelander <[email protected]>
6+
7+
All rights reserved. Use of this source code is governed by a
8+
BSD-style license that can be found in the LICENSE file.
9+
*/
10+
11+
#pragma once
12+
#include "pybind11.h"
13+
14+
#if defined(_MSC_VER)
15+
# pragma warning(push)
16+
# pragma warning(disable: 4100) // warning C4100: Unreferenced formal parameter
17+
# pragma warning(disable: 4127) // warning C4127: Conditional expression is constant
18+
#endif
19+
20+
NAMESPACE_BEGIN(pybind11)
21+
22+
template <typename type, typename... options>
23+
template <typename... Args, typename... Extra>
24+
class_<type, options...> &class_<type, options...>::def(detail::init_factory<Args...> &&init, const Extra&... extra) {
25+
std::move(init).execute(*this, extra...);
26+
return *this;
27+
}
28+
29+
NAMESPACE_BEGIN(detail)
30+
31+
inline void init_factory_no_nullptr(void *ptr) {
32+
if (!ptr) throw type_error("pybind11::init(): factory function returned nullptr");
33+
}
34+
35+
inline void init_factory_reset(instance *self) {
36+
// Reallocate the instance if there are any existing values
37+
bool need_reset = false;
38+
for (auto &v_h : values_and_holders(self))
39+
if (v_h) { need_reset = true; break; }
40+
41+
if (need_reset) {
42+
clear_instance((PyObject *) self);
43+
self->allocate_layout();
44+
}
45+
}
46+
47+
48+
template <typename CFunc, typename CReturn, typename AFuncIn, typename AReturn, typename... Args> struct init_factory {
49+
private:
50+
using CFuncType = typename std::remove_reference<CFunc>::type;
51+
using AFunc = conditional_t<std::is_void<AFuncIn>::value, void_type, AFuncIn>;
52+
using AFuncType = typename std::remove_reference<AFunc>::type;
53+
54+
template <typename Class> using Cpp = typename Class::type;
55+
template <typename Class> using Alias = typename Class::type_alias;
56+
template <typename Class> using Holder = typename Class::holder_type;
57+
58+
public:
59+
// Constructor with a single function/lambda to call
60+
init_factory(CFunc &&f) : class_factory(std::forward<CFunc>(f)) {}
61+
62+
// Constructor with two functions/lambdas, for a class with distinct class/alias factories: the
63+
// first is called when an alias is not needed, the second when the alias is needed. Requires
64+
// non-void AFunc.
65+
init_factory(CFunc &&c, AFunc &&a) :
66+
class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
67+
68+
// Add __init__ definition for a class that has no alias or has no separate alias factory
69+
template <typename Class, typename... Extra,
70+
enable_if_t<!Class::has_alias || std::is_void<AFuncIn>::value, int> = 0>
71+
void execute(Class &cl, const Extra&... extra) && {
72+
auto *cl_type = (PyTypeObject *) cl.ptr();
73+
#if defined(PYBIND11_CPP14)
74+
cl.def("__init__", [cl_type, func = std::move(class_factory)]
75+
#else
76+
CFuncType func(std::move(class_factory));
77+
cl.def("__init__", [cl_type, func]
78+
#endif
79+
(handle self, Args... args) {
80+
auto *inst = reinterpret_cast<instance *>(self.ptr());
81+
construct<Class>(inst, func(std::forward<Args>(args)...), cl_type);
82+
}, extra...);
83+
}
84+
85+
// Add __init__ definition for a class with an alias *and* distinct alias factory:
86+
template <typename Class, typename... Extra,
87+
enable_if_t<Class::has_alias && !std::is_void<AFuncIn>::value, int> = 0>
88+
void execute(Class &cl, const Extra&... extra) && {
89+
auto *cl_type = (PyTypeObject *) cl.ptr();
90+
#if defined(PYBIND11_CPP14)
91+
cl.def("__init__", [cl_type, class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
92+
#else
93+
CFuncType class_func(std::move(class_factory));
94+
AFuncType alias_func(std::move(alias_factory));
95+
cl.def("__init__", [cl_type, class_func, alias_func]
96+
#endif
97+
(handle self, Args... args) {
98+
auto *inst = reinterpret_cast<instance *>(self.ptr());
99+
if (Py_TYPE(inst) == cl_type)
100+
construct<Class>(inst, class_func(std::forward<Args>(args)...), cl_type);
101+
else
102+
construct<Class>(inst, alias_func(std::forward<Args>(args)...), cl_type);
103+
}, extra...);
104+
}
105+
106+
private:
107+
// Pointer assignment implementation:
108+
template <typename Class>
109+
static void construct_impl(instance *self, Cpp<Class> *ptr) {
110+
init_factory_reset(self);
111+
auto v_h = self->get_value_and_holder(get_type_info(Py_TYPE(self)));
112+
v_h.value_ptr() = ptr;
113+
}
114+
115+
// Takes a cpp class pointer and returns true if it actually a polymorphic alias instance.
116+
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
117+
static bool is_alias(Cpp<Class> *ptr) {
118+
return dynamic_cast<Alias<Class> *>(ptr) != nullptr;
119+
}
120+
// Failing fallback version for a no-alias class (always returns false)
121+
template <typename Class>
122+
constexpr static bool is_alias(void *) { return false; }
123+
124+
// Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. Returns true if the
125+
// constructor existed, false is the alias cannot be constructed that way.
126+
template <typename Class, enable_if_t<std::is_constructible<Alias<Class>, Cpp<Class> &&>::value, int> = 0>
127+
static bool construct_alias_from_cpp(instance *self, Cpp<Class> &&base) {
128+
new (self->get_value_and_holder().value_ptr()) Alias<Class>(std::move(base));
129+
return true;
130+
}
131+
// Base version for aliases without an appropriate constructor; does nothing and returns false.
132+
template <typename Class, enable_if_t<!std::is_constructible<Alias<Class>, Cpp<Class> &&>::value, int> = 0>
133+
static bool construct_alias_from_cpp(instance *, Cpp<Class> &&) { return false; }
134+
135+
// Error-generating fallback
136+
template <typename Class>
137+
static void construct(...) {
138+
static_assert(!std::is_same<Class, Class>::value /* always false */,
139+
"pybind11::init(): wrapped factory function must return a compatible pointer, "
140+
"holder, or value");
141+
}
142+
143+
// Pointer return v1: a factory function that returns a class pointer for a registered class
144+
// without an alias
145+
template <typename Class, enable_if_t<!Class::has_alias, int> = 0>
146+
static void construct(instance *self, Cpp<Class> *ptr, PyTypeObject *) {
147+
init_factory_no_nullptr(ptr);
148+
construct_impl<Class>(self, ptr);
149+
}
150+
151+
// Pointer return v2: a factory that always returns an alias instance ptr (like py::init_alias)
152+
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
153+
static void construct(instance *self, Alias<Class> *alias_ptr, PyTypeObject *) {
154+
construct_impl<Class>(self, static_cast<Cpp<Class> *>(alias_ptr));
155+
}
156+
157+
// Pointer return v3: returning a cpp class for a registered class with an alias. If we don't
158+
// need an alias, we simply use the cpp pointer. Otherwise, we try a dynamic_cast to see if the
159+
// cpp pointer is actually a polymorphic alias instance. If it isn't, but we can construct the
160+
// alias via an `Alias(Cpp &&)` constructor, we do. Otherwise we throw a type error.
161+
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
162+
static void construct(instance *self, Cpp<Class> *ptr, PyTypeObject *cl_type) {
163+
init_factory_no_nullptr(ptr);
164+
if (Py_TYPE(self) != cl_type) {
165+
// Inherited from on the Python side: an alias instance is required
166+
if (!is_alias<Class>(ptr)) {
167+
if (construct_alias_from_cpp<Class>(self, std::move(*ptr))) {
168+
delete ptr;
169+
return;
170+
}
171+
172+
delete ptr;
173+
throw type_error("pybind11::init(): factory function pointer could not be cast or "
174+
"converted to an alias instance");
175+
}
176+
}
177+
construct_impl<Class>(self, ptr);
178+
}
179+
180+
// Holder return: copy its pointer, and move or copy the returned holder into the new instance's
181+
// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
182+
// derived type (through those holder's implicit conversion from derived class holder constructors).
183+
template <typename Class>
184+
static void construct(instance *self, Holder<Class> holder, PyTypeObject *cl_type) {
185+
auto *ptr = holder_helper<Holder<Class>>::get(holder);
186+
// If we need an alias, check that the held pointer is actually an alias instance
187+
if (Class::has_alias && Py_TYPE(self) != cl_type && !is_alias<Class>(ptr))
188+
throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
189+
"is not an alias instance");
190+
191+
construct_impl<Class>(self, ptr);
192+
Class::init_instance(self, &holder);
193+
}
194+
195+
// return-by-value version 1: returning a cpp class by value when there is no alias
196+
template <typename Class, enable_if_t<!Class::has_alias, int> = 0>
197+
static void construct(instance *self, Cpp<Class> &&result, PyTypeObject *) {
198+
static_assert(std::is_move_constructible<Cpp<Class>>::value,
199+
"pybind11::init() return-by-value factory function requires a movable class");
200+
new (self->get_value_and_holder().value_ptr()) Cpp<Class>(std::move(result));
201+
}
202+
203+
// return-by-value version 2: returning a cpp class by value when there is an alias: the alias
204+
// must have an `Alias(Base &&)` constructor so that we can construct the alias from the base
205+
// when needed.
206+
template <typename Class, enable_if_t<Class::has_alias, int> = 0>
207+
static void construct(instance *self, Cpp<Class> &&result, PyTypeObject *cl_type) {
208+
static_assert(std::is_move_constructible<Cpp<Class>>::value,
209+
"pybind11::init() return-by-value factory function requires a movable class");
210+
if (Py_TYPE(self) != cl_type) {
211+
if (!construct_alias_from_cpp<Class>(self, std::move(result)))
212+
throw type_error("pybind11::init(): unable to convert returned instance to "
213+
"required alias class: no `Alias(Class &&)` constructor available");
214+
}
215+
else
216+
new (self->get_value_and_holder().value_ptr()) Cpp<Class>(std::move(result));
217+
}
218+
219+
// return-by-value version 3: the alias type itself--always initialize via the alias type (this
220+
// is the factory equivalent of py::init_alias<...>()).
221+
template <typename Class>
222+
static void construct(instance *self, Alias<Class> &&result, PyTypeObject *) {
223+
static_assert(std::is_move_constructible<Alias<Class>>::value,
224+
"pybind11::init() return-by-alias-value factory function requires a movable alias class");
225+
new (self->get_value_and_holder().value_ptr()) Alias<Class>(std::move(result));
226+
}
227+
228+
CFuncType class_factory;
229+
AFuncType alias_factory;
230+
};
231+
232+
template <typename Func> using init_factory_functype =
233+
conditional_t<std::is_function<remove_reference_t<Func>>::value, remove_reference_t<Func> *,
234+
conditional_t<is_function_pointer<remove_reference_t<Func>>::value, remove_reference_t<Func>,
235+
Func>>;
236+
237+
// Helper definition to infer the detail::init_factory template type from a callable object
238+
template <typename Func, typename Return, typename... Args>
239+
init_factory<init_factory_functype<Func>, Return, void, void, Args...> init_factory_decltype(Return (*)(Args...));
240+
241+
template <typename Return1, typename Return2, typename... Args1, typename... Args2>
242+
inline constexpr bool init_factory_require_matching_arguments(Return1 (*)(Args1...), Return2 (*)(Args2...)) {
243+
static_assert(sizeof...(Args1) == sizeof...(Args2),
244+
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
245+
static_assert(all_of<std::is_same<Args1, Args2>...>::value,
246+
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
247+
return true;
248+
}
249+
250+
template <typename CFunc, typename AFunc,
251+
typename CReturn, typename... CArgs, typename AReturn, typename... AArgs,
252+
bool = init_factory_require_matching_arguments((CReturn (*)(CArgs...)) nullptr, (AReturn (*)(AArgs...)) nullptr)>
253+
init_factory<init_factory_functype<CFunc>, CReturn, init_factory_functype<AFunc>, AReturn, CArgs...> init_factory_decltype(
254+
CReturn (*)(CArgs...), AReturn (*)(AArgs...));
255+
256+
template <typename... Func> using init_factory_t = decltype(init_factory_decltype<Func...>(
257+
(function_signature_t<Func> *) nullptr...));
258+
259+
NAMESPACE_END(detail)
260+
261+
/// Single-argument factory function constructor wrapper
262+
template <typename Func, typename Ret = detail::init_factory_t<Func>>
263+
Ret init(Func &&f) { return {std::forward<Func>(f)}; }
264+
265+
/// Dual-argument factory function: the first function is called when no alias is needed, the second
266+
/// when an alias is needed (i.e. due to python-side inheritance). Arguments must be identical.
267+
template <typename CFunc, typename AFunc, typename Ret = detail::init_factory_t<CFunc, AFunc>>
268+
Ret init(CFunc &&c, AFunc &&a) {
269+
return {std::forward<CFunc>(c), std::forward<AFunc>(a)};
270+
}
271+
272+
NAMESPACE_END(pybind11)
273+
274+
#if defined(_MSC_VER)
275+
# pragma warning(pop)
276+
#endif

0 commit comments

Comments
 (0)