Skip to content

Allow binding factory functions as constructors #805

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 5 commits into from
Aug 17, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ set(PYBIND11_HEADERS
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
Expand Down
122 changes: 115 additions & 7 deletions docs/advanced/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,8 @@ can now create a python class that inherits from ``Dog``:
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
=======================================

Expand Down Expand Up @@ -358,16 +360,124 @@ Custom constructors
===================

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

Expand All @@ -378,9 +488,7 @@ is short hand notation for
}
);

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:

Expand Down
4 changes: 1 addition & 3 deletions include/pybind11/attr.h
Original file line number Diff line number Diff line change
Expand Up @@ -116,8 +116,6 @@ enum op_id : int;
enum op_type : int;
struct undefined_t;
template <op_id id, op_type ot, typename L = undefined_t, typename R = undefined_t> struct op_;
template <typename... Args> struct init;
template <typename... Args> struct init_alias;
inline void keep_alive_impl(size_t Nurse, size_t Patient, function_call &call, handle ret);

/// Internal data structure which holds metadata about a keyword argument
Expand Down Expand Up @@ -223,7 +221,7 @@ struct type_record {
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;
Expand Down
43 changes: 30 additions & 13 deletions include/pybind11/cast.h
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ struct type_info {
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;
Expand Down Expand Up @@ -301,11 +301,15 @@ struct value_and_holder {
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} {}

Expand All @@ -323,22 +327,26 @@ struct value_and_holder {
? 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;
}
};

Expand Down Expand Up @@ -403,7 +411,7 @@ struct values_and_holders {
* 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);
Expand All @@ -413,6 +421,9 @@ PYBIND11_NOINLINE inline value_and_holder instance::get_value_and_holder(const t
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 "
Expand Down Expand Up @@ -560,7 +571,7 @@ inline PyThreadState *get_thread_state_unchecked() {

// Forward declarations
inline void keep_alive_impl(handle nurse, handle patient);
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value = true);
inline PyObject *make_new_instance(PyTypeObject *type);

class type_caster_generic {
public:
Expand Down Expand Up @@ -591,7 +602,7 @@ class type_caster_generic {
}
}

auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type, false /* don't allocate value */));
auto inst = reinterpret_steal<object>(make_new_instance(tinfo->type));
auto wrapper = reinterpret_cast<instance *>(inst.ptr());
wrapper->owned = false;
void *&valueptr = values_and_holders(wrapper).begin()->value_ptr();
Expand Down Expand Up @@ -647,8 +658,14 @@ class type_caster_generic {
protected:

// Base methods for generic caster; there are overridden in copyable_holder_caster
void load_value(const value_and_holder &v_h) {
value = v_h.value_ptr();
void load_value(value_and_holder &&v_h) {
auto *&vptr = v_h.value_ptr();
// Lazy allocation for unallocated values:
if (vptr == nullptr) {
auto *type = v_h.type ? v_h.type : typeinfo;
vptr = type->operator_new(type->type_size);
}
value = vptr;
}
bool try_implicit_casts(handle src, bool convert) {
for (auto &cast : typeinfo->implicit_casts) {
Expand Down Expand Up @@ -1448,7 +1465,7 @@ struct copyable_holder_caster : public type_caster_base<type> {
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>();
Expand Down
16 changes: 4 additions & 12 deletions include/pybind11/detail/class.h
Original file line number Diff line number Diff line change
Expand Up @@ -232,11 +232,10 @@ inline bool deregister_instance(instance *self, void *valptr, const type_info *t
return ret;
}

/// Instance creation function for all pybind11 types. It only allocates space for the C++ object
/// (or multiple objects, for Python-side inheritance from multiple pybind11 types), but doesn't
/// call the constructor -- an `__init__` function must do that (followed by an `init_instance`
/// to set up the holder and register the instance).
inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= true (in cast.h)*/) {
/// Instance creation function for all pybind11 types. It allocates the internal instance layout for
/// holding C++ objects and holders. Allocation is done lazily (the first time the instance is cast
/// to a reference or pointer), and initialization is done by an `__init__` function.
inline PyObject *make_new_instance(PyTypeObject *type) {
#if defined(PYPY_VERSION)
// PyPy gets tp_basicsize wrong (issue 2482) under multiple inheritance when the first inherited
// object is a a plain Python type (i.e. not derived from an extension type). Fix it.
Expand All @@ -251,13 +250,6 @@ inline PyObject *make_new_instance(PyTypeObject *type, bool allocate_value /*= t
inst->allocate_layout();

inst->owned = true;
// Allocate (if requested) the value pointers; otherwise leave them as nullptr
if (allocate_value) {
for (auto &v_h : values_and_holders(inst)) {
void *&vptr = v_h.value_ptr();
vptr = v_h.type->operator_new(v_h.type->type_size);
}
}

return self;
}
Expand Down
32 changes: 29 additions & 3 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -434,15 +434,16 @@ struct instance {
/// If true, get_internals().patients has an entry for this object
bool has_patients : 1;

/// Initializes all of the above type/values/holders data
/// Initializes all of the above type/values/holders data (but not the instance values themselves)
void allocate_layout();

/// Destroys/deallocates all of the above
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;
Expand Down Expand Up @@ -689,6 +690,31 @@ template <typename T>
struct is_input_iterator<T, void_t<decltype(*std::declval<T &>()), decltype(++std::declval<T &>())>>
: std::true_type {};

template <typename T> using is_function_pointer = bool_constant<
std::is_pointer<T>::value && std::is_function<typename std::remove_pointer<T>::type>::value>;

template <typename F> struct strip_function_object {
using type = typename remove_class<decltype(&F::operator())>::type;
};

// Extracts the function signature from a function, function pointer or lambda.
template <typename Function, typename F = remove_reference_t<Function>>
using function_signature_t = conditional_t<
std::is_function<F>::value,
F,
typename conditional_t<
std::is_pointer<F>::value || std::is_member_pointer<F>::value,
std::remove_pointer<F>,
strip_function_object<F>
>::type
>;

/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
/// pointer. Note that this can catch all sorts of other things, too; this is intended to be used
/// in a place where passing a lambda makes sense.
template <typename T> using is_lambda = satisfies_none_of<remove_reference_t<T>,
std::is_function, std::is_pointer, std::is_member_pointer>;

/// Ignore that a variable is unused in compiler warnings
inline void ignore_unused(const int *) { }

Expand Down
Loading