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.
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: the existing
templated, no-argument one wraps constructors, the no-template,
function-argument one wraps factory functions.
- 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.
- Reimplement factory instance clearing. The previous implementation
failed under python-side multiple inheritance: *each* inherited
type's factory init would clear the instance instead of only setting
its own type value. The new implementation here clears just the
relevant value pointer.
- dealloc is updated to explicitly set the leftover value pointer to
nullptr and the `holder_constructed` flag to false so that it can be
used to clear preallocated value without needing to rebuild the
instance internals data.
- Added various tests to test out new allocation/deallocation code.
- With preallocation now done lazily, init factory holders can
completely avoid the extra overhead of needing an extra
allocation/deallocation.
- Updated documentation to make factory constructors the default
advanced constructor style.
- If an `__init__` is called a second time, we have two choices: we can
throw away the first instance, replacing it with the second; or we can
ignore the second call. The latter is slightly easier, so do that.
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 0a63bf7..ac4e992 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -39,6 +39,7 @@
include/pybind11/detail/class.h
include/pybind11/detail/common.h
include/pybind11/detail/descr.h
+ include/pybind11/detail/init.h
include/pybind11/detail/typeid.h
include/pybind11/attr.h
include/pybind11/buffer_info.h
diff --git a/docs/advanced/classes.rst b/docs/advanced/classes.rst
index 20853be..cdac77d 100644
--- a/docs/advanced/classes.rst
+++ b/docs/advanced/classes.rst
@@ -322,6 +322,8 @@
See the file :file:`tests/test_virtual_functions.cpp` for complete examples
using both the duplication and templated trampoline approaches.
+.. _extended_aliases:
+
Extended trampoline class functionality
=======================================
@@ -358,16 +360,124 @@
===================
The syntax for binding constructors was previously introduced, but it only
-works when a constructor with the given parameters actually exists on the C++
-side. To extend this to more general cases, let's take a look at what actually
-happens under the hood: the following statement
+works when a constructor of the appropriate arguments actually exists on the
+C++ side. To extend this to more general cases, pybind11 offers two different
+approaches: binding factory functions, and placement-new creation.
+
+Factory function constructors
+-----------------------------
+
+It is possible to expose a Python-side constructor from a C++ function that
+returns a new object by value or pointer. For example, suppose you have a
+class like this:
+
+.. code-block:: cpp
+
+ class Example {
+ private:
+ Example(int); // private constructor
+ public:
+ // Factory function:
+ static Example create(int a) { return Example(a); }
+ };
+
+While it is possible to expose the ``create`` method to Python, it is often
+preferrable to expose it on the Python side as a constructor rather than a
+named static method. You can do this by calling ``.def(py::init(...))`` with
+the function reference returning the new instance passed as an argument. It is
+also possible to use this approach to bind a function returning a new instance
+by raw pointer or by the holder (e.g. ``std::unique_ptr``).
+
+The following example shows the different approaches:
+
+.. code-block:: cpp
+
+ class Example {
+ private:
+ Example(int); // private constructor
+ public:
+ // Factory function - returned by value:
+ static Example create(int a) { return Example(a); }
+
+ // These constructors are publicly callable:
+ Example(double);
+ Example(int, int);
+ Example(std::string);
+ };
+
+ py::class_<Example>(m, "Example")
+ // Bind the factory function as a constructor:
+ .def(py::init(&Example::create))
+ // Bind a lambda function returning a pointer wrapped in a holder:
+ .def(py::init([](std::string arg) {
+ return std::unique_ptr<Example>(new Example(arg));
+ }))
+ // Return a raw pointer:
+ .def(py::init([](int a, int b) { return new Example(a, b); }))
+ // You can mix the above with regular C++ constructor bindings as well:
+ .def(py::init<double>())
+ ;
+
+When the constructor is invoked from Python, pybind11 will call the factory
+function and store the resulting C++ instance in the Python instance.
+
+When combining factory functions constructors with :ref:`overriding_virtuals`
+there are two approaches. The first is to add a constructor to the alias class
+that takes a base value by rvalue-reference. If such a constructor is
+available, it will be used to construct an alias instance from the value
+returned by the factory function. The second option is to provide two factory
+functions to ``py::init()``: the first will be invoked when no alias class is
+required (i.e. when the class is being used but not inherited from in Python),
+and the second will be invoked when an alias is required.
+
+You can also specify a single factory function that always returns an alias
+instance: this will result in behaviour similar to ``py::init_alias<...>()``,
+as described in :ref:`extended_aliases`.
+
+The following example shows the different factory approaches for a class with
+an alias:
+
+.. code-block:: cpp
+
+ #include <pybind11/factory.h>
+ class Example {
+ public:
+ // ...
+ virtual ~Example() = default;
+ };
+ class PyExample : public Example {
+ public:
+ using Example::Example;
+ PyExample(Example &&base) : Example(std::move(base)) {}
+ };
+ py::class_<Example, PyExample>(m, "Example")
+ // Returns an Example pointer. If a PyExample is needed, the Example
+ // instance will be moved via the extra constructor in PyExample, above.
+ .def(py::init([]() { return new Example(); }))
+ // Two callbacks:
+ .def(py::init([]() { return new Example(); } /* no alias needed */,
+ []() { return new PyExample(); } /* alias needed */))
+ // *Always* returns an alias instance (like py::init_alias<>())
+ .def(py::init([]() { return new PyExample(); }))
+ ;
+
+Low-level placement-new construction
+------------------------------------
+
+A second approach for creating new instances use C++ placement new to construct
+an object in-place in preallocated memory. To do this, you simply bind a
+method name ``__init__`` that takes the class instance as the first argument by
+pointer or reference, then uses a placement-new constructor to construct the
+object in the pre-allocated (but uninitialized) memory.
+
+For example, instead of:
.. code-block:: cpp
py::class_<Example>(m, "Example")
.def(py::init<int>());
-is short hand notation for
+you could equivalently write:
.. code-block:: cpp
@@ -378,9 +488,7 @@
}
);
-In other words, :func:`init` creates an anonymous function that invokes an
-in-place constructor. Memory allocation etc. is already take care of beforehand
-within pybind11.
+which will invoke the constructor in-place at the pre-allocated memory.
.. _classes_with_non_public_destructors:
diff --git a/include/pybind11/attr.h b/include/pybind11/attr.h
index 54760cd..5634c35 100644
--- a/include/pybind11/attr.h
+++ b/include/pybind11/attr.h
@@ -223,7 +223,7 @@
void (*init_instance)(instance *, const void *) = nullptr;
/// Function pointer to class_<..>::dealloc
- void (*dealloc)(const detail::value_and_holder &) = nullptr;
+ void (*dealloc)(detail::value_and_holder &) = nullptr;
/// List of base classes of the newly created type
list bases;
diff --git a/include/pybind11/cast.h b/include/pybind11/cast.h
index 4b5a4bf..415079d 100644
--- a/include/pybind11/cast.h
+++ b/include/pybind11/cast.h
@@ -45,7 +45,7 @@
size_t type_size, holder_size_in_ptrs;
void *(*operator_new)(size_t);
void (*init_instance)(instance *, const void *);
- void (*dealloc)(const value_and_holder &v_h);
+ void (*dealloc)(value_and_holder &v_h);
std::vector<PyObject *(*)(PyObject *, PyTypeObject *)> implicit_conversions;
std::vector<std::pair<const std::type_info *, void *(*)(void *)>> implicit_casts;
std::vector<bool (*)(PyObject *, void *&)> *direct_conversions;
@@ -301,11 +301,15 @@
const detail::type_info *type;
void **vh;
+ // Main constructor for a found value/holder:
value_and_holder(instance *i, const detail::type_info *type, size_t vpos, size_t index) :
inst{i}, index{index}, type{type},
vh{inst->simple_layout ? inst->simple_value_holder : &inst->nonsimple.values_and_holders[vpos]}
{}
+ // Default constructor (used to signal a value-and-holder not found by get_value_and_holder())
+ value_and_holder() : inst{nullptr} {}
+
// Used for past-the-end iterator
value_and_holder(size_t index) : index{index} {}
@@ -323,22 +327,26 @@
? inst->simple_holder_constructed
: inst->nonsimple.status[index] & instance::status_holder_constructed;
}
- void set_holder_constructed() {
+ void set_holder_constructed(bool v = true) {
if (inst->simple_layout)
- inst->simple_holder_constructed = true;
- else
+ inst->simple_holder_constructed = v;
+ else if (v)
inst->nonsimple.status[index] |= instance::status_holder_constructed;
+ else
+ inst->nonsimple.status[index] &= (uint8_t) ~instance::status_holder_constructed;
}
bool instance_registered() const {
return inst->simple_layout
? inst->simple_instance_registered
: inst->nonsimple.status[index] & instance::status_instance_registered;
}
- void set_instance_registered() {
+ void set_instance_registered(bool v = true) {
if (inst->simple_layout)
- inst->simple_instance_registered = true;
- else
+ inst->simple_instance_registered = v;
+ else if (v)
inst->nonsimple.status[index] |= instance::status_instance_registered;
+ else
+ inst->nonsimple.status[index] &= (uint8_t) ~instance::status_instance_registered;
}
};
@@ -403,7 +411,7 @@
* The returned object should be short-lived: in particular, it must not outlive the called-upon
* instance.
*/
-PYBIND11_NOINLINE inline value_and_holder instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/) {
+PYBIND11_NOINLINE inline value_and_holder instance::get_value_and_holder(const type_info *find_type /*= nullptr default in common.h*/, bool throw_if_missing /*= true in common.h*/) {
// Optimize common case:
if (!find_type || Py_TYPE(this) == find_type->type)
return value_and_holder(this, find_type, 0, 0);
@@ -413,6 +421,9 @@
if (it != vhs.end())
return *it;
+ if (!throw_if_missing)
+ return value_and_holder();
+
#if defined(NDEBUG)
pybind11_fail("pybind11::detail::instance::get_value_and_holder: "
"type is not a pybind11 base of the given instance "
@@ -1454,7 +1465,7 @@
throw cast_error("Unable to load a custom holder type from a default-holder instance");
}
- bool load_value(const value_and_holder &v_h) {
+ bool load_value(value_and_holder &&v_h) {
if (v_h.holder_constructed()) {
value = v_h.value_ptr();
holder = v_h.holder<holder_type>();
diff --git a/include/pybind11/detail/common.h b/include/pybind11/detail/common.h
index 7397e2b..100873f 100644
--- a/include/pybind11/detail/common.h
+++ b/include/pybind11/detail/common.h
@@ -441,8 +441,9 @@
void deallocate_layout();
/// Returns the value_and_holder wrapper for the given type (or the first, if `find_type`
- /// omitted)
- value_and_holder get_value_and_holder(const type_info *find_type = nullptr);
+ /// omitted). Returns a default-constructed (with `.inst = nullptr`) object on failure if
+ /// `throw_if_missing` is false.
+ value_and_holder get_value_and_holder(const type_info *find_type = nullptr, bool throw_if_missing = true);
/// Bit values for the non-simple status flags
static constexpr uint8_t status_holder_constructed = 1;
diff --git a/include/pybind11/detail/init.h b/include/pybind11/detail/init.h
new file mode 100644
index 0000000..cefb32b
--- /dev/null
+++ b/include/pybind11/detail/init.h
@@ -0,0 +1,274 @@
+/*
+ pybind11/detail/init.h: init factory function implementation and support code.
+
+ Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
+
+ All rights reserved. Use of this source code is governed by a
+ BSD-style license that can be found in the LICENSE file.
+*/
+
+#pragma once
+
+#include "class.h"
+
+NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
+NAMESPACE_BEGIN(detail)
+NAMESPACE_BEGIN(initimpl)
+
+inline void no_nullptr(void *ptr) {
+ if (!ptr) throw type_error("pybind11::init(): factory function returned nullptr");
+}
+
+// Makes sure the `value` for the given value_and_holder is not preallocated (e.g. by a previous
+// old-style placement new `__init__` that requires a preallocated, uninitialized value). If
+// preallocated, deallocate. Returns the (null) value pointer reference ready for allocation.
+inline void *&deallocate(value_and_holder &v_h) {
+ if (v_h) v_h.type->dealloc(v_h);
+ return v_h.value_ptr();
+}
+
+PYBIND11_NOINLINE inline value_and_holder load_v_h(handle self_, type_info *tinfo) {
+ if (!self_ || !tinfo)
+ throw type_error("__init__(self, ...) called with invalid `self` argument");
+
+ auto *inst = reinterpret_cast<instance *>(self_.ptr());
+ auto result = inst->get_value_and_holder(tinfo, false);
+ if (!result.inst)
+ throw type_error("__init__(self, ...) called with invalid `self` argument");
+
+ return result;
+}
+
+
+// Implementing functions for py::init(...)
+template <typename Class> using Cpp = typename Class::type;
+template <typename Class> using Alias = typename Class::type_alias;
+template <typename Class> using Holder = typename Class::holder_type;
+
+template <typename Class> using is_alias_constructible = std::is_constructible<Alias<Class>, Cpp<Class> &&>;
+
+// Takes a Cpp pointer and returns true if it actually is a polymorphic Alias instance.
+template <typename Class, enable_if_t<Class::has_alias, int> = 0>
+bool is_alias(Cpp<Class> *ptr) {
+ return dynamic_cast<Alias<Class> *>(ptr) != nullptr;
+}
+// Failing fallback version of the above for a no-alias class (always returns false)
+template <typename /*Class*/>
+constexpr bool is_alias(void *) { return false; }
+
+// Attempts to constructs an alias using a `Alias(Cpp &&)` constructor. This allows types with
+// an alias to provide only a single Cpp factory function as long as the Alias can be
+// constructed from an rvalue reference of the base Cpp type. This means that Alias classes
+// can, when appropriate, simply define a `Alias(Cpp &&)` constructor rather than needing to
+// inherit all the base class constructors.
+template <typename Class>
+void construct_alias_from_cpp(std::true_type /*is_alias_constructible*/,
+ value_and_holder &v_h, Cpp<Class> &&base) {
+ deallocate(v_h) = new Alias<Class>(std::move(base));
+}
+template <typename Class>
+[[noreturn]] void construct_alias_from_cpp(std::false_type /*!is_alias_constructible*/,
+ value_and_holder &, Cpp<Class> &&) {
+ throw type_error("pybind11::init(): unable to convert returned instance to required "
+ "alias class: no `Alias<Class>(Class &&)` constructor available");
+}
+
+// Error-generating fallback for factories that don't match one of the below construction
+// mechanisms.
+template <typename Class>
+void construct(...) {
+ static_assert(!std::is_same<Class, Class>::value /* always false */,
+ "pybind11::init(): init function must return a compatible pointer, "
+ "holder, or value");
+}
+
+// Pointer return v1: the factory function returns a class pointer for a registered class.
+// If we don't need an alias (because this class doesn't have one, or because the final type is
+// inherited on the Python side) we can simply take over ownership. Otherwise we need to try to
+// construct an Alias from the returned base instance.
+template <typename Class>
+void construct(value_and_holder &v_h, Cpp<Class> *ptr, bool need_alias) {
+ no_nullptr(ptr);
+ if (Class::has_alias && need_alias && !is_alias<Class>(ptr)) {
+ // We're going to try to construct an alias by moving the cpp type. Whether or not
+ // that succeeds, we still need to destroy the original cpp pointer (either the
+ // moved away leftover, if the alias construction works, or the value itself if we
+ // throw an error), but we can't just call `delete ptr`: it might have a special
+ // deleter, or might be shared_from_this. So we construct a holder around it as if
+ // it was a normal instance, then steal the holder away into a local variable; thus
+ // the holder and destruction happens when we leave the C++ scope, and the holder
+ // class gets to handle the destruction however it likes.
+ deallocate(v_h) = ptr;
+ v_h.set_instance_registered(true); // To prevent init_instance from registering it
+ v_h.type->init_instance(v_h.inst, nullptr); // Set up the holder
+ Holder<Class> temp_holder(std::move(v_h.holder<Holder<Class>>())); // Steal the holder
+ v_h.type->dealloc(v_h); // Destroys the moved-out holder remains, resets value ptr to null
+ v_h.set_instance_registered(false);
+
+ construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(*ptr));
+ }
+ else {
+ // Otherwise the type isn't inherited, so we don't need an Alias and can just store the Cpp
+ // pointer directory:
+ deallocate(v_h) = ptr;
+ }
+}
+
+// Pointer return v2: a factory that always returns an alias instance ptr. We simply take over
+// ownership of the pointer.
+template <typename Class, enable_if_t<Class::has_alias, int> = 0>
+void construct(value_and_holder &v_h, Alias<Class> *alias_ptr, bool) {
+ no_nullptr(alias_ptr);
+ deallocate(v_h) = static_cast<Cpp<Class> *>(alias_ptr);
+}
+
+// Holder return: copy its pointer, and move or copy the returned holder into the new instance's
+// holder. This also handles types like std::shared_ptr<T> and std::unique_ptr<T> where T is a
+// derived type (through those holder's implicit conversion from derived class holder constructors).
+template <typename Class>
+void construct(value_and_holder &v_h, Holder<Class> holder, bool need_alias) {
+ auto *ptr = holder_helper<Holder<Class>>::get(holder);
+ // If we need an alias, check that the held pointer is actually an alias instance
+ if (Class::has_alias && need_alias && !is_alias<Class>(ptr))
+ throw type_error("pybind11::init(): construction failed: returned holder-wrapped instance "
+ "is not an alias instance");
+
+ deallocate(v_h) = ptr;
+ v_h.type->init_instance(v_h.inst, &holder);
+}
+
+// return-by-value version 1: returning a cpp class by value. If the class has an alias and an
+// alias is required the alias must have an `Alias(Cpp &&)` constructor so that we can construct
+// the alias from the base when needed (i.e. because of Python-side inheritance). When we don't
+// need it, we simply move-construct the cpp value into a new instance.
+template <typename Class>
+void construct(value_and_holder &v_h, Cpp<Class> &&result, bool need_alias) {
+ static_assert(std::is_move_constructible<Cpp<Class>>::value,
+ "pybind11::init() return-by-value factory function requires a movable class");
+ if (Class::has_alias && need_alias)
+ construct_alias_from_cpp<Class>(is_alias_constructible<Class>{}, v_h, std::move(result));
+ else
+ deallocate(v_h) = new Cpp<Class>(std::move(result));
+}
+
+// return-by-value version 2: returning a value of the alias type itself. We move-construct an
+// Alias instance (even if no the python-side inheritance is involved). The is intended for
+// cases where Alias initialization is always desired.
+template <typename Class>
+void construct(value_and_holder &v_h, Alias<Class> &&result, bool) {
+ static_assert(std::is_move_constructible<Alias<Class>>::value,
+ "pybind11::init() return-by-alias-value factory function requires a movable alias class");
+ deallocate(v_h) = new Alias<Class>(std::move(result));
+}
+
+// Implementation class for py::init(Func) and py::init(Func, AliasFunc)
+template <typename CFunc, typename AFuncIn, typename... Args> struct factory {
+private:
+ using CFuncType = typename std::remove_reference<CFunc>::type;
+ using AFunc = conditional_t<std::is_void<AFuncIn>::value, void_type, AFuncIn>;
+ using AFuncType = typename std::remove_reference<AFunc>::type;
+
+ CFuncType class_factory;
+ AFuncType alias_factory;
+
+public:
+ // Constructor with a single function/lambda to call; for classes without aliases or with
+ // aliases that can be move constructed from the base.
+ factory(CFunc &&f) : class_factory(std::forward<CFunc>(f)) {}
+
+ // Constructor with two functions/lambdas, for a class with distinct class/alias factories: the
+ // first is called when an alias is not needed, the second when the alias is needed. Requires
+ // non-void AFunc.
+ factory(CFunc &&c, AFunc &&a) :
+ class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
+
+ // Add __init__ definition for a class that either has no alias or has no separate alias
+ // factory; this always constructs the class itself. If the class is registered with an alias
+ // type and an alias instance is needed (i.e. because the final type is a Python class
+ // inheriting from the C++ type) the returned value needs to either already be an alias
+ // instance, or the alias needs to be constructible from a `Class &&` argument.
+ template <typename Class, typename... Extra,
+ enable_if_t<!Class::has_alias || std::is_void<AFuncIn>::value, int> = 0>
+ void execute(Class &cl, const Extra&... extra) && {
+ auto *cl_type = get_type_info(typeid(Cpp<Class>));
+ #if defined(PYBIND11_CPP14)
+ cl.def("__init__", [cl_type, func = std::move(class_factory)]
+ #else
+ CFuncType &func = class_factory;
+ cl.def("__init__", [cl_type, func]
+ #endif
+ (handle self_, Args... args) {
+ auto v_h = load_v_h(self_, cl_type);
+ // If this value is already registered it must mean __init__ is invoked multiple times;
+ // we really can't support that in C++, so just ignore the second __init__.
+ if (v_h.instance_registered()) return;
+
+ construct<Class>(v_h, func(std::forward<Args>(args)...), Py_TYPE(v_h.inst) != cl_type->type);
+ }, extra...);
+ }
+
+ // Add __init__ definition for a class with an alias *and* distinct alias factory; the former is
+ // called when the `self` type passed to `__init__` is the direct class (i.e. not inherited), the latter
+ // when `self` is a Python-side subtype.
+ template <typename Class, typename... Extra,
+ enable_if_t<Class::has_alias && !std::is_void<AFuncIn>::value, int> = 0>
+ void execute(Class &cl, const Extra&... extra) && {
+ auto *cl_type = get_type_info(typeid(Cpp<Class>));
+
+ #if defined(PYBIND11_CPP14)
+ cl.def("__init__", [cl_type, class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
+ #else
+ CFuncType &class_func = class_factory;
+ AFuncType &alias_func = alias_factory;
+ cl.def("__init__", [cl_type, class_func, alias_func]
+ #endif
+ (handle self_, Args... args) {
+ auto v_h = load_v_h(self_, cl_type);
+ if (v_h.instance_registered()) return; // (see comment above)
+
+ if (Py_TYPE(v_h.inst) == cl_type->type)
+ // If the instance type equals the registered type we don't have inheritance, so
+ // don't need the alias and can construct using the class function:
+ construct<Class>(v_h, class_func(std::forward<Args>(args)...), false);
+ else
+ construct<Class>(v_h, alias_func(std::forward<Args>(args)...), true);
+ }, extra...);
+ }
+};
+
+template <typename Func> using functype =
+ conditional_t<std::is_function<remove_reference_t<Func>>::value, remove_reference_t<Func> *,
+ conditional_t<is_function_pointer<remove_reference_t<Func>>::value, remove_reference_t<Func>,
+ Func>>;
+
+// Helper definition to infer the detail::initimpl::factory template types from a callable object
+template <typename Func, typename Return, typename... Args>
+factory<functype<Func>, void, Args...> func_decltype(Return (*)(Args...));
+
+// metatemplate that ensures the Class and Alias factories take identical arguments: we need to be
+// able to call either one with the given arguments (depending on the final instance type).
+template <typename Return1, typename Return2, typename... Args1, typename... Args2>
+inline constexpr bool require_matching_arguments(Return1 (*)(Args1...), Return2 (*)(Args2...)) {
+ static_assert(sizeof...(Args1) == sizeof...(Args2),
+ "pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
+ static_assert(all_of<std::is_same<Args1, Args2>...>::value,
+ "pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
+ return true;
+}
+
+// Unimplemented function provided only for its type signature (via `decltype`), which resolves to
+// the appropriate specialization of the above `init` struct with the appropriate function, argument
+// and return types.
+template <typename CFunc, typename AFunc,
+ typename CReturn, typename... CArgs, typename AReturn, typename... AArgs,
+ bool = require_matching_arguments((CReturn (*)(CArgs...)) nullptr, (AReturn (*)(AArgs...)) nullptr)>
+factory<functype<CFunc>, functype<AFunc>, CArgs...> func_decltype(CReturn (*)(CArgs...), AReturn (*)(AArgs...));
+
+// Resolves to the appropriate specialization of the `pybind11::detail::initimpl::factory<...>` for a
+// given init function or pair of class/alias init functions.
+template <typename... Func> using factory_t = decltype(func_decltype<Func...>(
+ (function_signature_t<Func> *) nullptr...));
+
+NAMESPACE_END(initimpl)
+NAMESPACE_END(detail)
+NAMESPACE_END(pybind11)
diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h
index 5689d5a..d168b67 100644
--- a/include/pybind11/pybind11.h
+++ b/include/pybind11/pybind11.h
@@ -43,6 +43,7 @@
#include "attr.h"
#include "options.h"
#include "detail/class.h"
+#include "detail/init.h"
NAMESPACE_BEGIN(PYBIND11_NAMESPACE)
@@ -198,6 +199,8 @@
a.descr = strdup(a.value.attr("__repr__")().cast<std::string>().c_str());
}
+ rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
+
/* Generate a proper function signature */
std::string signature;
size_t type_depth = 0, char_index = 0, type_index = 0, arg_index = 0;
@@ -239,6 +242,12 @@
.cast<std::string>() + ".";
#endif
signature += tinfo->type->tp_name;
+ } else if (rec->is_constructor && arg_index == 0 && detail::same_type(typeid(handle), *t) && rec->scope) {
+ // A py::init(...) constructor takes `self` as a `handle`; rewrite it to the type
+#if defined(PYPY_VERSION)
+ signature += rec->scope.attr("__module__").cast<std::string>() + ".";
+#endif
+ signature += ((PyTypeObject *) rec->scope.ptr())->tp_name;
} else {
std::string tname(t->name());
detail::clean_type_id(tname);
@@ -267,7 +276,6 @@
#endif
rec->signature = strdup(signature.c_str());
rec->args.shrink_to_fit();
- rec->is_constructor = !strcmp(rec->name, "__init__") || !strcmp(rec->name, "__setstate__");
rec->nargs = (std::uint16_t) args;
if (rec->sibling && PYBIND11_INSTANCE_METHOD_CHECK(rec->sibling.ptr()))
@@ -709,7 +717,11 @@
} else {
if (overloads->is_constructor) {
auto tinfo = get_type_info((PyTypeObject *) overloads->scope.ptr());
- tinfo->init_instance(reinterpret_cast<instance *>(parent.ptr()), nullptr);
+ auto *pi = reinterpret_cast<instance *>(parent.ptr());
+ auto v_h = pi->get_value_and_holder(tinfo);
+ if (!v_h.holder_constructed()) {
+ tinfo->init_instance(pi, nullptr);
+ }
}
return result.ptr();
}
@@ -1045,6 +1057,12 @@
return *this;
}
+ template <typename... Args, typename... Extra>
+ class_ &def(detail::initimpl::factory<Args...> &&init, const Extra&... extra) {
+ std::move(init).execute(*this, extra...);
+ return *this;
+ }
+
template <typename Func> class_& def_buffer(Func &&func) {
struct capture { Func func; };
capture *ptr = new capture { std::forward<Func>(func) };
@@ -1225,11 +1243,15 @@
}
/// Deallocates an instance; via holder, if constructed; otherwise via operator delete.
- static void dealloc(const detail::value_and_holder &v_h) {
- if (v_h.holder_constructed())
+ static void dealloc(detail::value_and_holder &v_h) {
+ if (v_h.holder_constructed()) {
v_h.holder<holder_type>().~holder_type();
- else
+ v_h.set_holder_constructed(false);
+ }
+ else {
detail::call_operator_delete(v_h.value_ptr<type>(), v_h.type->type_size);
+ }
+ v_h.value_ptr() = nullptr;
}
static detail::function_record *get_function_record(handle h) {
@@ -1328,6 +1350,23 @@
handle m_parent;
};
+/// Binds an existing constructor taking arguments Args...
+template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); }
+/// Like `init<Args...>()`, but the instance is always constructed through the alias class (even
+/// when not inheriting on the Python side).
+template <typename... Args> detail::init_alias<Args...> init_alias() { return detail::init_alias<Args...>(); }
+
+/// Binds a factory function as a constructor
+template <typename Func, typename Ret = detail::initimpl::factory_t<Func>>
+Ret init(Func &&f) { return {std::forward<Func>(f)}; }
+
+/// Dual-argument factory function: the first function is called when no alias is needed, the second
+/// when an alias is needed (i.e. due to python-side inheritance). Arguments must be identical.
+template <typename CFunc, typename AFunc, typename Ret = detail::initimpl::factory_t<CFunc, AFunc>>
+Ret init(CFunc &&c, AFunc &&a) {
+ return {std::forward<CFunc>(c), std::forward<AFunc>(a)};
+}
+
NAMESPACE_BEGIN(detail)
template <typename... Args> struct init {
template <typename Class, typename... Extra, enable_if_t<!Class::has_alias, int> = 0>
@@ -1431,9 +1470,6 @@
NAMESPACE_END(detail)
-template <typename... Args> detail::init<Args...> init() { return detail::init<Args...>(); }
-template <typename... Args> detail::init_alias<Args...> init_alias() { return detail::init_alias<Args...>(); }
-
/// Makes a python iterator from a first and past-the-end C++ InputIterator.
template <return_value_policy Policy = return_value_policy::reference_internal,
typename Iterator,
diff --git a/setup.py b/setup.py
index 395f83c..c67fa02 100644
--- a/setup.py
+++ b/setup.py
@@ -15,6 +15,7 @@
'include/pybind11/detail/class.h',
'include/pybind11/detail/common.h',
'include/pybind11/detail/descr.h',
+ 'include/pybind11/detail/init.h',
'include/pybind11/detail/typeid.h'
'include/pybind11/attr.h',
'include/pybind11/buffer_info.h',
diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt
index 18e6b9f..1e68a74 100644
--- a/tests/CMakeLists.txt
+++ b/tests/CMakeLists.txt
@@ -39,6 +39,7 @@
test_enum.cpp
test_eval.cpp
test_exceptions.cpp
+ test_factory_constructors.cpp
test_kwargs_and_defaults.cpp
test_local_bindings.cpp
test_methods_and_attributes.cpp
diff --git a/tests/conftest.py b/tests/conftest.py
index 3fe5023..f4c2282 100644
--- a/tests/conftest.py
+++ b/tests/conftest.py
@@ -211,6 +211,8 @@
'requires_eigen_and_scipy': skipif(not have_eigen or not scipy,
reason="eigen and/or scipy are not installed"),
'unsupported_on_pypy': skipif(pypy, reason="unsupported on PyPy"),
+ 'unsupported_on_py2': skipif(sys.version_info.major < 3,
+ reason="unsupported on Python 2.x"),
'gc_collect': gc_collect
}
diff --git a/tests/test_factory_constructors.cpp b/tests/test_factory_constructors.cpp
new file mode 100644
index 0000000..54792d9
--- /dev/null
+++ b/tests/test_factory_constructors.cpp
@@ -0,0 +1,336 @@
+/*
+ tests/test_factory_constructors.cpp -- tests construction from a factory function
+ via py::init_factory()
+
+ Copyright (c) 2017 Jason Rhinelander <jason@imaginary.ca>
+
+ 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"
+#include "constructor_stats.h"
+#include <cmath>
+
+// Classes for testing python construction via C++ factory function:
+// Not publically constructible, copyable, or movable:
+class TestFactory1 {
+ friend class TestFactoryHelper;
+ TestFactory1() : value("(empty)") { print_default_created(this); }
+ TestFactory1(int v) : value(std::to_string(v)) { print_created(this, value); }
+ TestFactory1(std::string v) : value(std::move(v)) { print_created(this, value); }
+ TestFactory1(TestFactory1 &&) = delete;
+ TestFactory1(const TestFactory1 &) = delete;
+ TestFactory1 &operator=(TestFactory1 &&) = delete;
+ TestFactory1 &operator=(const TestFactory1 &) = delete;
+public:
+ std::string value;
+ ~TestFactory1() { print_destroyed(this); }
+};
+// Non-public construction, but moveable:
+class TestFactory2 {
+ friend class TestFactoryHelper;
+ TestFactory2() : value("(empty2)") { print_default_created(this); }
+ TestFactory2(int v) : value(std::to_string(v)) { print_created(this, value); }
+ TestFactory2(std::string v) : value(std::move(v)) { print_created(this, value); }
+public:
+ TestFactory2(TestFactory2 &&m) { value = std::move(m.value); print_move_created(this); }
+ TestFactory2 &operator=(TestFactory2 &&m) { value = std::move(m.value); print_move_assigned(this); return *this; }
+ std::string value;
+ ~TestFactory2() { print_destroyed(this); }
+};
+// Mixed direct/factory construction:
+class TestFactory3 {
+protected:
+ friend class TestFactoryHelper;
+ TestFactory3() : value("(empty3)") { print_default_created(this); }
+ TestFactory3(int v) : value(std::to_string(v)) { print_created(this, value); }
+public:
+ TestFactory3(std::string v) : value(std::move(v)) { print_created(this, value); }
+ TestFactory3(TestFactory3 &&m) { value = std::move(m.value); print_move_created(this); }
+ TestFactory3 &operator=(TestFactory3 &&m) { value = std::move(m.value); print_move_assigned(this); return *this; }
+ std::string value;
+ virtual ~TestFactory3() { print_destroyed(this); }
+};
+// Inheritance test
+class TestFactory4 : public TestFactory3 {
+public:
+ TestFactory4() : TestFactory3() { print_default_created(this); }
+ TestFactory4(int v) : TestFactory3(v) { print_created(this, v); }
+ virtual ~TestFactory4() { print_destroyed(this); }
+};
+// Another class for an invalid downcast test
+class TestFactory5 : public TestFactory3 {
+public:
+ TestFactory5(int i) : TestFactory3(i) { print_created(this, i); }
+ virtual ~TestFactory5() { print_destroyed(this); }
+};
+
+class TestFactory6 {
+protected:
+ int value;
+ bool alias = false;
+public:
+ TestFactory6(int i) : value{i} { print_created(this, i); }
+ TestFactory6(TestFactory6 &&f) { print_move_created(this); value = f.value; alias = f.alias; }
+ TestFactory6(const TestFactory6 &f) { print_copy_created(this); value = f.value; alias = f.alias; }
+ virtual ~TestFactory6() { print_destroyed(this); }
+ virtual int get() { return value; }
+ bool has_alias() { return alias; }
+};
+class PyTF6 : public TestFactory6 {
+public:
+ // Special constructor that allows the factory to construct a PyTF6 from a TestFactory6 only
+ // when an alias is needed:
+ PyTF6(TestFactory6 &&base) : TestFactory6(std::move(base)) { alias = true; print_created(this, "move", value); }
+ PyTF6(int i) : TestFactory6(i) { alias = true; print_created(this, i); }
+ PyTF6(PyTF6 &&f) : TestFactory6(std::move(f)) { print_move_created(this); }
+ PyTF6(const PyTF6 &f) : TestFactory6(f) { print_copy_created(this); }
+ PyTF6(std::string s) : TestFactory6((int) s.size()) { alias = true; print_created(this, s); }
+ virtual ~PyTF6() { print_destroyed(this); }
+ int get() override { PYBIND11_OVERLOAD(int, TestFactory6, get, /*no args*/); }
+};
+
+class TestFactory7 {
+protected:
+ int value;
+ bool alias = false;
+public:
+ TestFactory7(int i) : value{i} { print_created(this, i); }
+ TestFactory7(TestFactory7 &&f) { print_move_created(this); value = f.value; alias = f.alias; }
+ TestFactory7(const TestFactory7 &f) { print_copy_created(this); value = f.value; alias = f.alias; }
+ virtual ~TestFactory7() { print_destroyed(this); }
+ virtual int get() { return value; }
+ bool has_alias() { return alias; }
+};
+class PyTF7 : public TestFactory7 {
+public:
+ PyTF7(int i) : TestFactory7(i) { alias = true; print_created(this, i); }
+ PyTF7(PyTF7 &&f) : TestFactory7(std::move(f)) { print_move_created(this); }
+ PyTF7(const PyTF7 &f) : TestFactory7(f) { print_copy_created(this); }
+ virtual ~PyTF7() { print_destroyed(this); }
+ int get() override { PYBIND11_OVERLOAD(int, TestFactory7, get, /*no args*/); }
+};
+
+
+class TestFactoryHelper {
+public:
+ // Non-movable, non-copyable type:
+ // Return via pointer:
+ static TestFactory1 *construct1() { return new TestFactory1(); }
+ // Holder:
+ static std::unique_ptr<TestFactory1> construct1(int a) { return std::unique_ptr<TestFactory1>(new TestFactory1(a)); }
+ // pointer again
+ static TestFactory1 *construct1_string(std::string a) { return new TestFactory1(a); }
+
+ // Moveable type:
+ // pointer:
+ static TestFactory2 *construct2() { return new TestFactory2(); }
+ // holder:
+ static std::unique_ptr<TestFactory2> construct2(int a) { return std::unique_ptr<TestFactory2>(new TestFactory2(a)); }
+ // by value moving:
+ static TestFactory2 construct2(std::string a) { return TestFactory2(a); }
+
+ // shared_ptr holder type:
+ // pointer:
+ static TestFactory3 *construct3() { return new TestFactory3(); }
+ // holder:
+ static std::shared_ptr<TestFactory3> construct3(int a) { return std::shared_ptr<TestFactory3>(new TestFactory3(a)); }
+};
+
+TEST_SUBMODULE(factory_constructors, m) {
+
+ // Define various trivial types to allow simpler overload resolution:
+ py::module m_tag = m.def_submodule("tag");
+#define MAKE_TAG_TYPE(Name) \
+ struct Name##_tag {}; \
+ py::class_<Name##_tag>(m_tag, #Name "_tag").def(py::init<>()); \
+ m_tag.attr(#Name) = py::cast(Name##_tag{})
+ MAKE_TAG_TYPE(pointer);
+ MAKE_TAG_TYPE(unique_ptr);
+ MAKE_TAG_TYPE(move);
+ MAKE_TAG_TYPE(shared_ptr);
+ MAKE_TAG_TYPE(derived);
+ MAKE_TAG_TYPE(TF4);
+ MAKE_TAG_TYPE(TF5);
+ MAKE_TAG_TYPE(null_ptr);
+ MAKE_TAG_TYPE(base);
+ MAKE_TAG_TYPE(invalid_base);
+ MAKE_TAG_TYPE(alias);
+ MAKE_TAG_TYPE(unaliasable);
+ MAKE_TAG_TYPE(mixed);
+
+ // test_init_factory_basic, test_bad_type
+ py::class_<TestFactory1>(m, "TestFactory1")
+ .def(py::init([](unique_ptr_tag, int v) { return TestFactoryHelper::construct1(v); }))
+ .def(py::init(&TestFactoryHelper::construct1_string)) // raw function pointer
+ .def(py::init([](pointer_tag) { return TestFactoryHelper::construct1(); }))
+ .def(py::init([](py::handle, int v, py::handle) { return TestFactoryHelper::construct1(v); }))
+ .def_readwrite("value", &TestFactory1::value)
+ ;
+ py::class_<TestFactory2>(m, "TestFactory2")
+ .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct2(v); }))
+ .def(py::init([](unique_ptr_tag, std::string v) { return TestFactoryHelper::construct2(v); }))
+ .def(py::init([](move_tag) { return TestFactoryHelper::construct2(); }))
+ .def_readwrite("value", &TestFactory2::value)
+ ;
+
+ // Stateful & reused:
+ int c = 1;
+ auto c4a = [c](pointer_tag, TF4_tag, int a) { return new TestFactory4(a);};
+
+ // test_init_factory_basic, test_init_factory_casting
+ py::class_<TestFactory3, std::shared_ptr<TestFactory3>>(m, "TestFactory3")
+ .def(py::init([](pointer_tag, int v) { return TestFactoryHelper::construct3(v); }))
+ .def(py::init([](shared_ptr_tag) { return TestFactoryHelper::construct3(); }))
+ .def("__init__", [](TestFactory3 &self, std::string v) { new (&self) TestFactory3(v); }) // placement-new ctor
+
+ // factories returning a derived type:
+ .def(py::init(c4a)) // derived ptr
+ .def(py::init([](pointer_tag, TF5_tag, int a) { return new TestFactory5(a); }))
+ // derived shared ptr:
+ .def(py::init([](shared_ptr_tag, TF4_tag, int a) { return std::make_shared<TestFactory4>(a); }))
+ .def(py::init([](shared_ptr_tag, TF5_tag, int a) { return std::make_shared<TestFactory5>(a); }))
+
+ // Returns nullptr:
+ .def(py::init([](null_ptr_tag) { return (TestFactory3 *) nullptr; }))
+
+ .def_readwrite("value", &TestFactory3::value)
+ ;
+
+ // test_init_factory_casting
+ py::class_<TestFactory4, TestFactory3, std::shared_ptr<TestFactory4>>(m, "TestFactory4")
+ .def(py::init(c4a)) // pointer
+ ;
+
+ // Doesn't need to be registered, but registering makes getting ConstructorStats easier:
+ py::class_<TestFactory5, TestFactory3, std::shared_ptr<TestFactory5>>(m, "TestFactory5");
+
+ // test_init_factory_alias
+ // Alias testing
+ py::class_<TestFactory6, PyTF6>(m, "TestFactory6")
+ .def(py::init([](base_tag, int i) { return TestFactory6(i); }))
+ .def(py::init([](alias_tag, int i) { return PyTF6(i); }))
+ .def(py::init([](alias_tag, std::string s) { return PyTF6(s); }))
+ .def(py::init([](alias_tag, pointer_tag, int i) { return new PyTF6(i); }))
+ .def(py::init([](base_tag, pointer_tag, int i) { return new TestFactory6(i); }))
+ .def(py::init([](base_tag, alias_tag, pointer_tag, int i) { return (TestFactory6 *) new PyTF6(i); }))
+
+ .def("get", &TestFactory6::get)
+ .def("has_alias", &TestFactory6::has_alias)
+
+ .def_static("get_cstats", &ConstructorStats::get<TestFactory6>, py::return_value_policy::reference)
+ .def_static("get_alias_cstats", &ConstructorStats::get<PyTF6>, py::return_value_policy::reference)
+ ;
+
+ // test_init_factory_dual
+ // Separate alias constructor testing
+ py::class_<TestFactory7, PyTF7, std::shared_ptr<TestFactory7>>(m, "TestFactory7")
+ .def(py::init(
+ [](int i) { return TestFactory7(i); },
+ [](int i) { return PyTF7(i); }))
+ .def(py::init(
+ [](pointer_tag, int i) { return new TestFactory7(i); },
+ [](pointer_tag, int i) { return new PyTF7(i); }))
+ .def(py::init(
+ [](mixed_tag, int i) { return new TestFactory7(i); },
+ [](mixed_tag, int i) { return PyTF7(i); }))
+ .def(py::init(
+ [](mixed_tag, std::string s) { return TestFactory7((int) s.size()); },
+ [](mixed_tag, std::string s) { return new PyTF7((int) s.size()); }))
+ .def(py::init(
+ [](base_tag, pointer_tag, int i) { return new TestFactory7(i); },
+ [](base_tag, pointer_tag, int i) { return (TestFactory7 *) new PyTF7(i); }))
+ .def(py::init(
+ [](alias_tag, pointer_tag, int i) { return new PyTF7(i); },
+ [](alias_tag, pointer_tag, int i) { return new PyTF7(10*i); }))
+ .def(py::init(
+ [](shared_ptr_tag, base_tag, int i) { return std::make_shared<TestFactory7>(i); },
+ [](shared_ptr_tag, base_tag, int i) { auto *p = new PyTF7(i); return std::shared_ptr<TestFactory7>(p); }))
+ .def(py::init(
+ [](shared_ptr_tag, invalid_base_tag, int i) { return std::make_shared<TestFactory7>(i); },
+ [](shared_ptr_tag, invalid_base_tag, int i) { return std::make_shared<TestFactory7>(i); })) // <-- invalid alias factory
+
+ .def("get", &TestFactory7::get)
+ .def("has_alias", &TestFactory7::has_alias)
+
+ .def_static("get_cstats", &ConstructorStats::get<TestFactory7>, py::return_value_policy::reference)
+ .def_static("get_alias_cstats", &ConstructorStats::get<PyTF7>, py::return_value_policy::reference)
+ ;
+
+ // test_placement_new_alternative
+ // Class with a custom new operator but *without* a placement new operator (issue #948)
+ class NoPlacementNew {
+ public:
+ NoPlacementNew(int i) : i(i) { }
+ static void *operator new(std::size_t s) {
+ auto *p = ::operator new(s);
+ py::print("operator new called, returning", reinterpret_cast<uintptr_t>(p));
+ return p;
+ }
+ static void operator delete(void *p) {
+ py::print("operator delete called on", reinterpret_cast<uintptr_t>(p));
+ ::operator delete(p);
+ }
+ int i;
+ };
+ // Workaround for a `py::init<args>` on a class without placement new support
+ py::class_<NoPlacementNew>(m, "NoPlacementNew")
+ .def(py::init([]() { return new NoPlacementNew(100); }))
+ .def_readwrite("i", &NoPlacementNew::i)
+ ;
+
+
+ // test_reallocations
+ // Class that has verbose operator_new/operator_delete calls
+ struct NoisyAlloc {
+ NoisyAlloc(int i) { py::print(py::str("NoisyAlloc(int {})").format(i)); }
+ NoisyAlloc(double d) { py::print(py::str("NoisyAlloc(double {})").format(d)); }
+ ~NoisyAlloc() { py::print("~NoisyAlloc()"); }
+
+ static void *operator new(size_t s) { py::print("noisy new"); return ::operator new(s); }
+ static void *operator new(size_t, void *p) { py::print("noisy placement new"); return p; }
+ static void operator delete(void *p, size_t) { py::print("noisy delete"); ::operator delete(p); }
+ static void operator delete(void *, void *) { py::print("noisy placement delete"); }
+#if defined(_MSC_VER) && _MSC_VER < 1910
+ // MSVC 2015 bug: the above "noisy delete" isn't invoked (fixed in MSVC 2017)
+ static void operator delete(void *p) { py::print("noisy delete"); ::operator delete(p); }
+#endif
+ };
+ py::class_<NoisyAlloc>(m, "NoisyAlloc")
+ // Since these overloads have the same number of arguments, the dispatcher will try each of
+ // them until the arguments convert. Thus we can get a pre-allocation here when passing a
+ // single non-integer:
+ .def("__init__", [](NoisyAlloc *a, int i) { new (a) NoisyAlloc(i); }) // Regular constructor, runs first, requires preallocation
+ .def(py::init([](double d) { return new NoisyAlloc(d); }))
+
+ // The two-argument version: first the factory pointer overload.
+ .def(py::init([](int i, int) { return new NoisyAlloc(i); }))
+ // Return-by-value:
+ .def(py::init([](double d, int) { return NoisyAlloc(d); }))
+ // Old-style placement new init; requires preallocation
+ .def("__init__", [](NoisyAlloc &a, double d, double) { new (&a) NoisyAlloc(d); })
+ // Requires deallocation of previous overload preallocated value:
+ .def(py::init([](int i, double) { return new NoisyAlloc(i); }))
+ // Regular again: requires yet another preallocation
+ .def("__init__", [](NoisyAlloc &a, int i, std::string) { new (&a) NoisyAlloc(i); })
+ ;
+
+
+
+
+ // static_assert testing (the following def's should all fail with appropriate compilation errors):
+#if 0
+ struct BadF1Base {};
+ struct BadF1 : BadF1Base {};
+ struct PyBadF1 : BadF1 {};
+ py::class_<BadF1, PyBadF1, std::shared_ptr<BadF1>> bf1(m, "BadF1");
+ // wrapped factory function must return a compatible pointer, holder, or value
+ bf1.def(py::init([]() { return 3; }));
+ // incompatible factory function pointer return type
+ bf1.def(py::init([]() { static int three = 3; return &three; }));
+ // incompatible factory function std::shared_ptr<T> return type: cannot convert shared_ptr<T> to holder
+ // (non-polymorphic base)
+ bf1.def(py::init([]() { return std::shared_ptr<BadF1Base>(new BadF1()); }));
+#endif
+}
diff --git a/tests/test_factory_constructors.py b/tests/test_factory_constructors.py
new file mode 100644
index 0000000..1d0679c
--- /dev/null
+++ b/tests/test_factory_constructors.py
@@ -0,0 +1,448 @@
+import pytest
+import re
+
+from pybind11_tests import factory_constructors as m
+from pybind11_tests.factory_constructors import tag
+from pybind11_tests import ConstructorStats
+
+
+def test_init_factory_basic():
+ """Tests py::init_factory() wrapper around various ways of returning the object"""
+
+ cstats = [ConstructorStats.get(c) for c in [m.TestFactory1, m.TestFactory2, m.TestFactory3]]
+ cstats[0].alive() # force gc
+ n_inst = ConstructorStats.detail_reg_inst()
+
+ x1 = m.TestFactory1(tag.unique_ptr, 3)
+ assert x1.value == "3"
+ y1 = m.TestFactory1(tag.pointer)
+ assert y1.value == "(empty)"
+ z1 = m.TestFactory1("hi!")
+ assert z1.value == "hi!"
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 3
+
+ x2 = m.TestFactory2(tag.move)
+ assert x2.value == "(empty2)"
+ y2 = m.TestFactory2(tag.pointer, 7)
+ assert y2.value == "7"
+ z2 = m.TestFactory2(tag.unique_ptr, "hi again")
+ assert z2.value == "hi again"
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 6
+
+ x3 = m.TestFactory3(tag.shared_ptr)
+ assert x3.value == "(empty3)"
+ y3 = m.TestFactory3(tag.pointer, 42)
+ assert y3.value == "42"
+ z3 = m.TestFactory3("bye")
+ assert z3.value == "bye"
+
+ with pytest.raises(TypeError) as excinfo:
+ m.TestFactory3(tag.null_ptr)
+ assert (str(excinfo.value) ==
+ "pybind11::init(): factory function returned nullptr")
+
+ assert [i.alive() for i in cstats] == [3, 3, 3]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 9
+
+ del x1, y2, y3, z3
+ assert [i.alive() for i in cstats] == [2, 2, 1]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 5
+ del x2, x3, y1, z1, z2
+ assert [i.alive() for i in cstats] == [0, 0, 0]
+ assert ConstructorStats.detail_reg_inst() == n_inst
+
+ assert [i.values() for i in cstats] == [
+ ["3", "hi!"],
+ ["7", "hi again"],
+ ["42", "bye"]
+ ]
+ assert [i.default_constructions for i in cstats] == [1, 1, 1]
+
+
+def test_init_factory_signature(msg):
+ with pytest.raises(TypeError) as excinfo:
+ m.TestFactory1("invalid", "constructor", "arguments")
+ assert msg(excinfo.value) == """
+ __init__(): incompatible constructor arguments. The following argument types are supported:
+ 1. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int)
+ 2. m.factory_constructors.TestFactory1(arg0: str)
+ 3. m.factory_constructors.TestFactory1(arg0: m.factory_constructors.tag.pointer_tag)
+ 4. m.factory_constructors.TestFactory1(arg0: handle, arg1: int, arg2: handle)
+
+ Invoked with: 'invalid', 'constructor', 'arguments'
+ """ # noqa: E501 line too long
+
+ assert msg(m.TestFactory1.__init__.__doc__) == """
+ __init__(*args, **kwargs)
+ Overloaded function.
+
+ 1. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.unique_ptr_tag, arg1: int) -> None
+
+ 2. __init__(self: m.factory_constructors.TestFactory1, arg0: str) -> None
+
+ 3. __init__(self: m.factory_constructors.TestFactory1, arg0: m.factory_constructors.tag.pointer_tag) -> None
+
+ 4. __init__(self: m.factory_constructors.TestFactory1, arg0: handle, arg1: int, arg2: handle) -> None
+ """ # noqa: E501 line too long
+
+
+def test_init_factory_casting():
+ """Tests py::init_factory() wrapper with various upcasting and downcasting returns"""
+
+ cstats = [ConstructorStats.get(c) for c in [m.TestFactory3, m.TestFactory4, m.TestFactory5]]
+ cstats[0].alive() # force gc
+ n_inst = ConstructorStats.detail_reg_inst()
+
+ # Construction from derived references:
+ a = m.TestFactory3(tag.pointer, tag.TF4, 4)
+ assert a.value == "4"
+ b = m.TestFactory3(tag.shared_ptr, tag.TF4, 5)
+ assert b.value == "5"
+ c = m.TestFactory3(tag.pointer, tag.TF5, 6)
+ assert c.value == "6"
+ d = m.TestFactory3(tag.shared_ptr, tag.TF5, 7)
+ assert d.value == "7"
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 4
+
+ # Shared a lambda with TF3:
+ e = m.TestFactory4(tag.pointer, tag.TF4, 8)
+ assert e.value == "8"
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 5
+ assert [i.alive() for i in cstats] == [5, 3, 2]
+
+ del a
+ assert [i.alive() for i in cstats] == [4, 2, 2]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 4
+
+ del b, c, e
+ assert [i.alive() for i in cstats] == [1, 0, 1]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 1
+
+ del d
+ assert [i.alive() for i in cstats] == [0, 0, 0]
+ assert ConstructorStats.detail_reg_inst() == n_inst
+
+ assert [i.values() for i in cstats] == [
+ ["4", "5", "6", "7", "8"],
+ ["4", "5", "8"],
+ ["6", "7"]
+ ]
+
+
+def test_init_factory_alias():
+ """Tests py::init_factory() wrapper with value conversions and alias types"""
+
+ cstats = [m.TestFactory6.get_cstats(), m.TestFactory6.get_alias_cstats()]
+ cstats[0].alive() # force gc
+ n_inst = ConstructorStats.detail_reg_inst()
+
+ a = m.TestFactory6(tag.base, 1)
+ assert a.get() == 1
+ assert not a.has_alias()
+ b = m.TestFactory6(tag.alias, "hi there")
+ assert b.get() == 8
+ assert b.has_alias()
+ c = m.TestFactory6(tag.alias, 3)
+ assert c.get() == 3
+ assert c.has_alias()
+ d = m.TestFactory6(tag.alias, tag.pointer, 4)
+ assert d.get() == 4
+ assert d.has_alias()
+ e = m.TestFactory6(tag.base, tag.pointer, 5)
+ assert e.get() == 5
+ assert not e.has_alias()
+ f = m.TestFactory6(tag.base, tag.alias, tag.pointer, 6)
+ assert f.get() == 6
+ assert f.has_alias()
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 6
+ assert [i.alive() for i in cstats] == [6, 4]
+
+ del a, b, e
+ assert [i.alive() for i in cstats] == [3, 3]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 3
+ del f, c, d
+ assert [i.alive() for i in cstats] == [0, 0]
+ assert ConstructorStats.detail_reg_inst() == n_inst
+
+ class MyTest(m.TestFactory6):
+ def __init__(self, *args):
+ m.TestFactory6.__init__(self, *args)
+
+ def get(self):
+ return -5 + m.TestFactory6.get(self)
+
+ # Return Class by value, moved into new alias:
+ z = MyTest(tag.base, 123)
+ assert z.get() == 118
+ assert z.has_alias()
+
+ # Return alias by value, moved into new alias:
+ y = MyTest(tag.alias, "why hello!")
+ assert y.get() == 5
+ assert y.has_alias()
+
+ # Return Class by pointer, moved into new alias then original destroyed:
+ x = MyTest(tag.base, tag.pointer, 47)
+ assert x.get() == 42
+ assert x.has_alias()
+
+ assert ConstructorStats.detail_reg_inst() == n_inst + 3
+ assert [i.alive() for i in cstats] == [3, 3]
+ del x, y, z
+ assert [i.alive() for i in cstats] == [0, 0]
+ assert ConstructorStats.detail_reg_inst() == n_inst
+
+ assert [i.values() for i in cstats] == [
+ ["1", "8", "3", "4", "5", "6", "123", "10", "47"],
+ ["hi there", "3", "4", "6", "move", "123", "why hello!", "move", "47"]
+ ]
+
+
+def test_init_factory_dual():
+ """Tests init factory functions with dual main/alias factory functions"""
+ from pybind11_tests.factory_constructors import TestFactory7
+
+ cstats = [TestFactory7.get_cstats(), TestFactory7.get_alias_cstats()]
+ cstats[0].alive() # force gc
+ n_inst = ConstructorStats.detail_reg_inst()
+
+ class PythFactory7(TestFactory7):
+ def get(self):
+ return 100 + TestFactory7.get(self)
+
+ a1 = TestFactory7(1)
+ a2 = PythFactory7(2)
+ assert a1.get() == 1
+ assert a2.get() == 102
+ assert not a1.has_alias()
+ assert a2.has_alias()
+
+ b1 = TestFactory7(tag.pointer, 3)
+ b2 = PythFactory7(tag.pointer, 4)
+ assert b1.get() == 3
+ assert b2.get() == 104
+ assert not b1.has_alias()
+ assert b2.has_alias()
+
+ c1 = TestFactory7(tag.mixed, 5)
+ c2 = PythFactory7(tag.mixed, 6)
+ assert c1.get() == 5
+ assert c2.get() == 106
+ assert not c1.has_alias()
+ assert c2.has_alias()
+
+ d1 = TestFactory7(tag.base, tag.pointer, 7)
+ d2 = PythFactory7(tag.base, tag.pointer, 8)
+ assert d1.get() == 7
+ assert d2.get() == 108
+ assert not d1.has_alias()
+ assert d2.has_alias()
+
+ # Both return an alias; the second multiplies the value by 10:
+ e1 = TestFactory7(tag.alias, tag.pointer, 9)
+ e2 = PythFactory7(tag.alias, tag.pointer, 10)
+ assert e1.get() == 9
+ assert e2.get() == 200
+ assert e1.has_alias()
+ assert e2.has_alias()
+
+ f1 = TestFactory7(tag.shared_ptr, tag.base, 11)
+ f2 = PythFactory7(tag.shared_ptr, tag.base, 12)
+ assert f1.get() == 11
+ assert f2.get() == 112
+ assert not f1.has_alias()
+ assert f2.has_alias()
+
+ g1 = TestFactory7(tag.shared_ptr, tag.invalid_base, 13)
+ assert g1.get() == 13
+ assert not g1.has_alias()
+ with pytest.raises(TypeError) as excinfo:
+ PythFactory7(tag.shared_ptr, tag.invalid_base, 14)
+ assert (str(excinfo.value) ==
+ "pybind11::init(): construction failed: returned holder-wrapped instance is not an "
+ "alias instance")
+
+ assert [i.alive() for i in cstats] == [13, 7]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 13
+
+ del a1, a2, b1, d1, e1, e2
+ assert [i.alive() for i in cstats] == [7, 4]
+ assert ConstructorStats.detail_reg_inst() == n_inst + 7
+ del b2, c1, c2, d2, f1, f2, g1
+ assert [i.alive() for i in cstats] == [0, 0]
+ assert ConstructorStats.detail_reg_inst() == n_inst
+
+ assert [i.values() for i in cstats] == [
+ ["1", "2", "3", "4", "5", "6", "7", "8", "9", "100", "11", "12", "13", "14"],
+ ["2", "4", "6", "8", "9", "100", "12"]
+ ]
+
+
+def test_no_placement_new(capture):
+ """Tests a workaround for `py::init<...>` with a class that doesn't support placement new."""
+ with capture:
+ b = m.NoPlacementNew()
+
+ found = re.search(r'^operator new called, returning (\d+)\n$', str(capture))
+ assert found
+ assert b.i == 100
+ with capture:
+ del b
+ pytest.gc_collect()
+ assert capture == "operator delete called on " + found.group(1)
+
+
+def test_multiple_inheritance():
+ class MITest(m.TestFactory1, m.TestFactory2):
+ def __init__(self):
+ m.TestFactory1.__init__(self, tag.unique_ptr, 33)
+ m.TestFactory2.__init__(self, tag.move)
+
+ a = MITest()
+ assert m.TestFactory1.value.fget(a) == "33"
+ assert m.TestFactory2.value.fget(a) == "(empty2)"
+
+
+def create_and_destroy(*args):
+ a = m.NoisyAlloc(*args)
+ print("---")
+ del a
+ pytest.gc_collect()
+
+
+def strip_comments(s):
+ return re.sub(r'\s+#.*', '', s)
+
+
+def test_reallocations(capture, msg):
+ """When the constructor is overloaded, previous overloads can require a preallocated value.
+ This test makes sure that such preallocated values only happen when they might be necessary,
+ and that they are deallocated properly"""
+
+ pytest.gc_collect()
+
+ with capture:
+ create_and_destroy(1)
+ assert msg(capture) == """
+ noisy new
+ noisy placement new
+ NoisyAlloc(int 1)
+ ---
+ ~NoisyAlloc()
+ noisy delete
+ """
+ with capture:
+ create_and_destroy(1.5)
+ assert msg(capture) == strip_comments("""
+ noisy new # allocation required to attempt first overload
+ noisy new # pointer factory calling "new", part 1: allocation
+ NoisyAlloc(double 1.5) # ... part two, invoking constructor
+ noisy delete # have to dealloc before stashing factory-generated pointer
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+ with capture:
+ create_and_destroy(2, 3)
+ assert msg(capture) == strip_comments("""
+ noisy new # pointer factory calling "new", allocation
+ NoisyAlloc(int 2) # constructor
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+ with capture:
+ create_and_destroy(2.5, 3)
+ assert msg(capture) == strip_comments("""
+ NoisyAlloc(double 2.5) # construction (local func variable: operator_new not called)
+ noisy new # return-by-value "new" part 1: allocation
+ ~NoisyAlloc() # moved-away local func variable destruction
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+ with capture:
+ create_and_destroy(3.5, 4.5)
+ assert msg(capture) == strip_comments("""
+ noisy new # preallocation needed before invoking placement-new overload
+ noisy placement new # Placement new
+ NoisyAlloc(double 3.5) # construction
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+ with capture:
+ create_and_destroy(4, 0.5)
+ assert msg(capture) == strip_comments("""
+ noisy new # preallocation needed before invoking placement-new overload
+ noisy new # Factory pointer allocation
+ NoisyAlloc(int 4) # factory pointer construction
+ noisy delete # deallocation of preallocated storage
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+ with capture:
+ create_and_destroy(5, "hi")
+ assert msg(capture) == strip_comments("""
+ noisy new # preallocation needed before invoking first placement new
+ noisy placement new # Placement new in the second placement new overload
+ NoisyAlloc(int 5) # construction
+ ---
+ ~NoisyAlloc() # Destructor
+ noisy delete # operator delete
+ """)
+
+
+@pytest.unsupported_on_py2
+def test_invalid_self():
+ """Tests invocation of the pybind-registered base class with an invalid `self` argument. You
+ can only actually do this on Python 3: Python 2 raises an exception itself if you try."""
+ class NotPybindDerived(object):
+ pass
+
+ # Attempts to initialize with an invalid type passed as `self`:
+ class BrokenTF1(m.TestFactory1):
+ def __init__(self, bad):
+ if bad == 1:
+ a = m.TestFactory2(tag.pointer, 1)
+ m.TestFactory1.__init__(a, tag.pointer)
+ elif bad == 2:
+ a = NotPybindDerived()
+ m.TestFactory1.__init__(a, tag.pointer)
+
+ # Same as above, but for a class with an alias:
+ class BrokenTF6(m.TestFactory6):
+ def __init__(self, bad):
+ if bad == 1:
+ a = m.TestFactory2(tag.pointer, 1)
+ m.TestFactory6.__init__(a, tag.base, 1)
+ elif bad == 2:
+ a = m.TestFactory2(tag.pointer, 1)
+ m.TestFactory6.__init__(a, tag.alias, 1)
+ elif bad == 3:
+ m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.base, 1)
+ elif bad == 4:
+ m.TestFactory6.__init__(NotPybindDerived.__new__(NotPybindDerived), tag.alias, 1)
+
+ for arg in (1, 2):
+ with pytest.raises(TypeError) as excinfo:
+ BrokenTF1(arg)
+ assert (str(excinfo.value) ==
+ "__init__(self, ...) called with invalid `self` argument")
+
+ for arg in (1, 2, 3, 4):
+ with pytest.raises(TypeError) as excinfo:
+ BrokenTF6(arg)
+ assert (str(excinfo.value) ==
+ "__init__(self, ...) called with invalid `self` argument")
diff --git a/tests/test_multiple_inheritance.py b/tests/test_multiple_inheritance.py
index 398687c..80b6dae 100644
--- a/tests/test_multiple_inheritance.py
+++ b/tests/test_multiple_inheritance.py
@@ -71,9 +71,9 @@
MI2.__init__(self, i, j)
class MI4(MI3, m.Base2):
- def __init__(self, i, j, k):
- MI3.__init__(self, j, k)
- m.Base2.__init__(self, i)
+ def __init__(self, i, j):
+ MI3.__init__(self, i, j)
+ # m.Base2 is already initialized (via MI2)
class MI5(m.Base2, B1, m.Base1):
def __init__(self, i, j):
@@ -127,10 +127,10 @@
assert mi3.foo() == 5
assert mi3.bar() == 6
- mi4 = MI4(7, 8, 9)
+ mi4 = MI4(7, 8)
assert mi4.v() == 1
- assert mi4.foo() == 8
- assert mi4.bar() == 7
+ assert mi4.foo() == 7
+ assert mi4.bar() == 8
mi5 = MI5(10, 11)
assert mi5.v() == 1
diff --git a/tests/test_virtual_functions.cpp b/tests/test_virtual_functions.cpp
index 4127d68..80ebe4c 100644
--- a/tests/test_virtual_functions.cpp
+++ b/tests/test_virtual_functions.cpp
@@ -234,6 +234,7 @@
py::class_<A2, PyA2>(m, "A2")
.def(py::init_alias<>())
+ .def(py::init([](int) { return new PyA2(); }))
.def("f", &A2::f);
m.def("call_f", [](A2 *a2) { a2->f(); });
@@ -444,4 +445,3 @@
.def(py::init<>());
};
-
diff --git a/tests/test_virtual_functions.py b/tests/test_virtual_functions.py
index 138be63..b91ebfa 100644
--- a/tests/test_virtual_functions.py
+++ b/tests/test_virtual_functions.py
@@ -128,11 +128,19 @@
m.call_f(a2)
del a2
pytest.gc_collect()
+ a3 = m.A2(1)
+ m.call_f(a3)
+ del a3
+ pytest.gc_collect()
assert capture == """
PyA2.PyA2()
PyA2.f()
A2.f()
PyA2.~PyA2()
+ PyA2.PyA2()
+ PyA2.f()
+ A2.f()
+ PyA2.~PyA2()
"""
# Python subclass version