Skip to content

Commit 19d0654

Browse files
committed
Allow binding factory functions as constructors
This allows you to use: cls.def(py::init_factory(&factory_function)); where `factory_function` is some pointer, holder, value, or handle-generating factory function of the type that `cls` binds. Various compile-time checks are performed to ensure the function is valid, and various run-time type checks where necessary (i.e. when using a python-object-returning function, or when a dynamic_cast is needed for downcasting a pointer). The feature is optional, and requires including the <pybind11/factory.h> header.
1 parent eac1ce2 commit 19d0654

File tree

9 files changed

+811
-1
lines changed

9 files changed

+811
-1
lines changed

CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ set(PYBIND11_HEADERS
4646
include/pybind11/options.h
4747
include/pybind11/eigen.h
4848
include/pybind11/eval.h
49+
include/pybind11/factory.h
4950
include/pybind11/functional.h
5051
include/pybind11/numpy.h
5152
include/pybind11/operators.h

docs/advanced/classes.rst

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -366,6 +366,51 @@ In other words, :func:`init` creates an anonymous function that invokes an
366366
in-place constructor. Memory allocation etc. is already take care of beforehand
367367
within pybind11.
368368

369+
Factory function constructors
370+
=============================
371+
372+
When binding a C++ type that creates new instances through a factory function
373+
or static method, it is sometimes desirable to bind C++ factory function as a Python
374+
constructor rather than a Python factory function. This is available through
375+
the ``py::init_factory`` wrapper, available when including the extra header
376+
``pybind11/factory.h``:
377+
378+
.. code-block:: cpp
379+
380+
#include <pybind11/factory.h>
381+
class Example {
382+
// ...
383+
static Example *create(int a) { return new Example(a); }
384+
};
385+
py::class_<Example>(m, "Example")
386+
// Bind an existing pointer-returning factory function:
387+
.def(py::init_factory(&Example::create))
388+
// Similar, but returns the pointer wrapped in a holder:
389+
.def(py::init_factory([](std::string arg) {
390+
return std::unique_ptr<Example>(new Example(arg, "another arg"));
391+
}))
392+
// Can overload these with regular constructors, too:
393+
.def(py::init<double>())
394+
;
395+
396+
When the constructor is invoked from Python, pybind11 will call the factory
397+
function and store the resulting C++ instance in the Python instance.
398+
399+
In addition to the examples shown above, ``py::init_factory`` supports
400+
up-casting or down-casting returned derived or base class pointers,
401+
respectively. The latter will raise an exception if the factory function
402+
pointer cannot be cast (via ``dynamic_cast``) to the required instance. Up-
403+
and down-casting is also permitted for ``std::shared_ptr`` factory return
404+
values.
405+
406+
Factory functions that return an object by value are also supported as long as
407+
the type is moveable or copyable.
408+
409+
Finally, factory functions may return existing an existing Python object via a
410+
``py::object`` wrapper instance; a run-time check is performed during
411+
construction that only allows a Python object of type being created (i.e. a
412+
Python ``Example`` instance in the example above).
413+
369414
.. _classes_with_non_public_destructors:
370415

371416
Non-public destructors

include/pybind11/attr.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ struct undefined_t;
115115
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
116116
template <typename... Args> struct init;
117117
template <typename... Args> struct init_alias;
118+
template <typename Func, typename Return, typename... Args> struct init_factory;
118119
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);
119120

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

include/pybind11/factory.h

Lines changed: 258 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,258 @@
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+
NAMESPACE_BEGIN(pybind11)
15+
16+
template <typename type, typename... options>
17+
template <typename... Args, typename... Extra>
18+
class_<type, options...> &class_<type, options...>::def(detail::init_factory<Args...> &&init, const Extra&... extra) {
19+
std::move(init).execute(*this, extra...);
20+
return *this;
21+
}
22+
23+
NAMESPACE_BEGIN(detail)
24+
25+
template <typename Func, typename Return, typename... Args> struct init_factory {
26+
private:
27+
using PlainReturn = typename std::remove_pointer<Return>::type;
28+
using ForwardReturn = typename std::add_rvalue_reference<Return>::type;
29+
30+
template <typename Class> using Cpp = typename Class::type;
31+
template <typename Class> using Alias = typename Class::type_alias;
32+
template <typename Class> using Inst = typename Class::instance_type;
33+
template <typename Class> using Holder = typename Class::holder_type;
34+
35+
template <typename Class, typename SFINAE = void> struct alias_constructible : std::false_type {};
36+
template <typename Class> struct alias_constructible<Class, enable_if_t<Class::has_alias &&
37+
std::is_convertible<Return, Alias<Class>>::value && std::is_constructible<Alias<Class>, Alias<Class> &&>::value>>
38+
: std::true_type {};
39+
template <typename Class> using cpp_constructible = bool_constant<
40+
!(Class::has_alias && std::is_base_of<Alias<Class>, Return>::value) &&
41+
std::is_convertible<Return, Cpp<Class>>::value && std::is_constructible<Cpp<Class>, Cpp<Class> &&>::value>;
42+
43+
template <typename Class, typename = Holder<Class>, typename = Return> struct is_shared_base : std::false_type {};
44+
template <typename Class, typename R>
45+
struct is_shared_base<Class, std::shared_ptr<Cpp<Class>>, std::shared_ptr<R>> : std::is_base_of<R, Cpp<Class>> {};
46+
47+
// We accept a return value in the following categories, in order of precedence:
48+
struct wraps_pointer_tag {};
49+
struct wraps_holder_tag {};
50+
struct wraps_base_shared_ptr_tag {};
51+
struct wraps_pyobject_tag {};
52+
struct wraps_value_tag {};
53+
struct invalid_factory_return_type {};
54+
55+
// Resolve the combination of Class and Return value to exactly one of the above tags:
56+
template <typename Class> using factory_type =
57+
// a pointer of the actual type, a derived type, or a base type:
58+
conditional_t<all_of<std::is_pointer<Return>, any_of<
59+
std::is_base_of<Cpp<Class>, PlainReturn>, std::is_base_of<PlainReturn, Cpp<Class>>>>::value,
60+
wraps_pointer_tag,
61+
// a holder (including upcasting if supported by the holder (e.g. shared_ptr and unique_ptr))
62+
conditional_t<std::is_convertible<Return, Holder<Class>>::value,
63+
wraps_holder_tag,
64+
// shared_ptr to a base or derived type (only accepted if this type's holder is also shared_ptr)
65+
conditional_t<is_shared_base<Class>::value,
66+
wraps_base_shared_ptr_tag,
67+
// a python object (with compatible type checking and failure at runtime):
68+
conditional_t<std::is_convertible<Return, handle>::value,
69+
wraps_pyobject_tag,
70+
// Accept-by-value: a return convertible to the cpp type and/or the alias:
71+
conditional_t<alias_constructible<Class>::value || cpp_constructible<Class>::value,
72+
wraps_value_tag,
73+
invalid_factory_return_type>>>>>;
74+
75+
public:
76+
// Constructor: takes the function/lambda to call
77+
init_factory(Func &&f) : f(std::forward<Func>(f)) {}
78+
79+
template <typename Class, typename... Extra>
80+
void execute(Class &cl, const Extra&... extra) && {
81+
// Some checks against various types of failure that we can detect at compile time:
82+
static_assert(!std::is_same<factory_type<Class>, invalid_factory_return_type>::value,
83+
"pybind11::init_factory(): wrapped factory function must return a compatible pointer, "
84+
"holder, python object, or value");
85+
86+
PyTypeObject *cl_type = (PyTypeObject *) cl.ptr();
87+
#if defined(PYBIND11_CPP14) || defined(_MSC_VER)
88+
cl.def("__init__", [cl_type, func = std::move(f)]
89+
#else
90+
Func func = std::move(f);
91+
cl.def("__init__", [cl_type, func]
92+
#endif
93+
(handle self, Args... args) {
94+
auto *inst = (Inst<Class> *) self.ptr();
95+
construct<Class>(inst, func(std::forward<Args>(args)...), cl_type, factory_type<Class>());
96+
}, extra...);
97+
}
98+
99+
protected:
100+
template <typename Class> static void dealloc(Inst<Class> *self) {
101+
// Reset/unallocate the existing values
102+
clear_instance((PyObject *) self);
103+
self->value = nullptr;
104+
self->owned = true;
105+
self->holder_constructed = false;
106+
}
107+
108+
template <typename Class>
109+
static void construct(Inst<Class> *self, PlainReturn *result, PyTypeObject *, wraps_pointer_tag) {
110+
// We were given a pointer to CppClass (or some derived or base type). If a base type, try
111+
// a dynamic_cast to the Cpp; for derived types do a static_cast. We then dealloc the
112+
// existing value and replace it with the given pointer; the dispatcher will then set up the
113+
// holder for us after we return from the lambda.
114+
115+
constexpr bool downcast = std::is_base_of<PlainReturn, Cpp<Class>>::value &&
116+
!std::is_base_of<Cpp<Class>, PlainReturn>::value;
117+
118+
if (!result) throw type_error("__init__() factory function returned a null pointer");
119+
Cpp<Class> *ptr;
120+
if (downcast) {
121+
ptr = dynamic_cast<Cpp<Class> *>(result);
122+
if (!ptr) {
123+
delete result;
124+
throw type_error("__init__() factory failed: could not cast base class pointer");
125+
}
126+
}
127+
else {
128+
ptr = static_cast<Cpp<Class> *>(result);
129+
}
130+
131+
dealloc<Class>(self);
132+
self->value = ptr;
133+
register_instance(self);
134+
}
135+
136+
template <typename Class>
137+
static void construct(Inst<Class> *self, Holder<Class> holder, PyTypeObject *, wraps_holder_tag) {
138+
// We were returned a holder; copy its pointer, and move/copy the holder into place.
139+
dealloc<Class>(self);
140+
self->value = holder_helper<Holder<Class>>::get(holder);
141+
Class::init_holder((PyObject *) self, &holder);
142+
register_instance(self);
143+
}
144+
145+
template <typename Class, typename T>
146+
static void construct(Inst<Class> *self, std::shared_ptr<T> holder, PyTypeObject *cl_type, wraps_base_shared_ptr_tag) {
147+
// We have a shared_ptr<T> where T is a base of Cpp, and our holder is shared_ptr<Cpp>
148+
Holder<Class> h = std::dynamic_pointer_cast<Cpp<Class>>(holder);
149+
if (!h)
150+
throw type_error("__init__() factory failed: could not cast shared base class pointer");
151+
construct<Class>(self, std::move(h), cl_type, wraps_holder_tag());
152+
}
153+
154+
template <typename Class>
155+
static void construct(Inst<Class> *self, handle result, PyTypeObject *cl_type, wraps_pyobject_tag tag) {
156+
// We were given a raw handle; steal it and forward to the py::object version
157+
construct<Class>(self, reinterpret_steal<object>(result), cl_type, tag);
158+
}
159+
template <typename Class>
160+
static void construct(Inst<Class> *self, object result, PyTypeObject *, wraps_pyobject_tag) {
161+
// Lambda returned a py::object (or something derived from it)
162+
163+
// Make sure we actually got something
164+
if (!result)
165+
throw type_error("__init__() factory function returned a null python object");
166+
167+
auto *result_inst = (Inst<Class> *) result.ptr();
168+
auto type = Py_TYPE(self);
169+
170+
// Make sure the factory function gave us exactly the right type (we don't allow
171+
// up/down-casting here):
172+
if (Py_TYPE(result_inst) != type)
173+
throw type_error(std::string("__init__() factory function should return '") + type->tp_name +
174+
"', not '" + Py_TYPE(result_inst)->tp_name + "'");
175+
// The factory function must give back a unique reference:
176+
if (result.ref_count() != 1)
177+
throw type_error("__init__() factory function returned an object with multiple references");
178+
// Guard against accidentally specifying a reference r.v. policy or similar:
179+
if (!result_inst->owned)
180+
throw type_error("__init__() factory function returned an unowned reference");
181+
182+
// Steal the instance internals:
183+
dealloc<Class>(self);
184+
std::swap(self->value, result_inst->value);
185+
std::swap(self->weakrefs, result_inst->weakrefs);
186+
if (type->tp_dictoffset != 0)
187+
std::swap(*_PyObject_GetDictPtr((PyObject *) self), *_PyObject_GetDictPtr((PyObject *) result_inst));
188+
// Now steal the holder
189+
Class::init_holder((PyObject *) self, &result_inst->holder);
190+
// Find the instance we just stole and update its PyObject from `result` to `self`
191+
auto range = get_internals().registered_instances.equal_range(self->value);
192+
for (auto it = range.first; it != range.second; ++it) {
193+
if (type == Py_TYPE(it->second)) {
194+
it->second = self;
195+
break;
196+
}
197+
}
198+
}
199+
200+
// return-by-value version 1: no alias or return not convertible to the alias:
201+
template <typename Class, enable_if_t<!alias_constructible<Class>::value, int> = 0>
202+
static void construct(Inst<Class> *self, Return &&result, PyTypeObject *cl_type, wraps_value_tag) {
203+
// Fail if we require an alias (i.e. if we're inherited from on the Python side)
204+
if (Class::has_alias && Py_TYPE(self) != cl_type)
205+
throw type_error("__init__() factory failed: cannot construct required alias class from factory return value");
206+
construct_cpp<Class>(self, std::forward<Return>(result));
207+
}
208+
209+
// return-by-value version 2: the alias type itself or something convertible to it but not (directly) to the cpp type;
210+
// always initialize via the alias type:
211+
template <typename Class, enable_if_t<alias_constructible<Class>::value && !cpp_constructible<Class>::value, int> = 0>
212+
static void construct(Inst<Class> *self, Return &&result, PyTypeObject *, wraps_value_tag) {
213+
construct_alias<Class>(self, std::forward<Return>(result));
214+
}
215+
216+
// return-by-value version 3: the return is convertible to both class and alias; construct via
217+
// alias if we're being used as a subclass, otherwise construct via cpp class.
218+
template <typename Class, enable_if_t<alias_constructible<Class>::value && cpp_constructible<Class>::value, int> = 0>
219+
static void construct(Inst<Class> *self, Return &&result, PyTypeObject *cl_type, wraps_value_tag) {
220+
// Use calls (rather than constructing directly) to properly trigger implicit conversion
221+
if (Py_TYPE(self) != cl_type)
222+
construct_alias<Class>(self, std::forward<Return>(result));
223+
else
224+
construct_cpp<Class>(self, std::forward<Return>(result));
225+
}
226+
template <typename Class> static void construct_alias(Inst<Class> *self, Alias<Class> result) {
227+
new (self->value) Alias<Class>(std::move(result));
228+
}
229+
template <typename Class> static void construct_cpp(Inst<Class> *self, Cpp<Class> result) {
230+
new (self->value) Cpp<Class>(std::move(result));
231+
}
232+
233+
Func f;
234+
};
235+
236+
// Helper definition to infer the detail::init_factory template type from a callable object
237+
template <typename Func, typename Return, typename... Args>
238+
init_factory<Func, Return, Args...> init_factory_decltype(Return (*)(Args...));
239+
template <typename Func> using init_factory_t = decltype(init_factory_decltype<Func>(
240+
(typename detail::remove_class<decltype(&std::remove_reference<Func>::type::operator())>::type *) nullptr));
241+
242+
NAMESPACE_END(detail)
243+
244+
/// Construct a factory function constructor wrapper from a vanilla function pointer
245+
template <typename Return, typename... Args>
246+
detail::init_factory<Return (*)(Args...), Return, Args...> init_factory(Return (*f)(Args...)) {
247+
return f;
248+
}
249+
/// Construct a factory function constructor wrapper from a lambda function (possibly with internal state)
250+
template <typename Func, typename = detail::enable_if_t<
251+
detail::satisfies_none_of<
252+
typename std::remove_reference<Func>::type,
253+
std::is_function, std::is_pointer, std::is_member_pointer
254+
>::value>
255+
>
256+
detail::init_factory_t<Func> init_factory(Func &&f) { return std::forward<Func>(f); }
257+
258+
NAMESPACE_END(pybind11)

include/pybind11/pybind11.h

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -709,7 +709,7 @@ class cpp_function : public function {
709709
PyErr_SetString(PyExc_TypeError, msg.c_str());
710710
return nullptr;
711711
} else {
712-
if (overloads->is_constructor) {
712+
if (overloads->is_constructor && !((instance_essentials<void> *) parent.ptr())->holder_constructed) {
713713
/* When a constructor ran successfully, the corresponding
714714
holder type (e.g. std::unique_ptr) must still be initialized. */
715715
auto tinfo = get_type_info(Py_TYPE(parent.ptr()));
@@ -1007,6 +1007,10 @@ class class_ : public detail::generic_type {
10071007
return *this;
10081008
}
10091009

1010+
// Implementation in pybind11/factory.h (which isn't included by default!)
1011+
template <typename... Args, typename... Extra>
1012+
class_ &def(detail::init_factory<Args...> &&init, const Extra&... extra);
1013+
10101014
template <typename Func> class_& def_buffer(Func &&func) {
10111015
struct capture { Func func; };
10121016
capture *ptr = new capture { std::forward<Func>(func) };
@@ -1155,6 +1159,8 @@ class class_ : public detail::generic_type {
11551159
init_holder_helper(inst, (const holder_type *) holder_ptr, inst->value);
11561160
}
11571161

1162+
template <typename, typename, typename...> friend struct detail::init_factory;
1163+
11581164
static void dealloc(PyObject *inst_) {
11591165
instance_type *inst = (instance_type *) inst_;
11601166
if (inst->holder_constructed)

setup.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@
2121
'include/pybind11/descr.h',
2222
'include/pybind11/eigen.h',
2323
'include/pybind11/eval.h',
24+
'include/pybind11/factory.h',
2425
'include/pybind11/functional.h',
2526
'include/pybind11/numpy.h',
2627
'include/pybind11/operators.h',

tests/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,7 @@ set(PYBIND11_TEST_FILES
3939
test_enum.cpp
4040
test_eval.cpp
4141
test_exceptions.cpp
42+
test_factory_constructors.cpp
4243
test_inheritance.cpp
4344
test_issues.cpp
4445
test_kwargs_and_defaults.cpp

0 commit comments

Comments
 (0)