Skip to content

Brace initialization for simple POD classes #1015

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 2 commits into from
Aug 22, 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
56 changes: 41 additions & 15 deletions docs/advanced/classes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -381,12 +381,12 @@ class like this:
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``).
While it is possible to create a straightforward binding of the static
``create`` method, it may sometimes be preferable to expose it as a constructor
on the Python side. This can be accomplished 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:

Expand Down Expand Up @@ -421,18 +421,20 @@ The following example shows the different approaches:
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.
When combining factory functions constructors with :ref:`virtual function
trampolines <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`.
as described in the :ref:`extended trampoline class documentation
<extended_aliases>`.

The following example shows the different factory approaches for a class with
an alias:
Expand Down Expand Up @@ -490,6 +492,30 @@ you could equivalently write:

which will invoke the constructor in-place at the pre-allocated memory.

Brace initialization
--------------------

``pybind11::init<>`` internally uses C++11 brace initialization to call the
constructor of the target class. This means that it can be used to bind
*implicit* constructors as well:

.. code-block:: cpp

struct Aggregate {
int a;
std::string b;
};

py::class_<Aggregate>(m, "Aggregate")
.def(py::init<int, const std::string &>());

.. note::

Note that brace initialization preferentially invokes constructor overloads
taking a ``std::initializer_list``. In the rare event that this causes an
issue, you can work around it by using ``py::init(...)`` with a lambda
function that constructs the new object as desired.

.. _classes_with_non_public_destructors:

Non-public destructors
Expand Down
10 changes: 5 additions & 5 deletions include/pybind11/detail/init.h
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ template <typename... Args> struct constructor {
// we really can't support that in C++, so just ignore the second __init__.
if (v_h.instance_registered()) return;

construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
construct<Class>(v_h, new Cpp<Class>{std::forward<Args>(args)...}, false);
}, extra...);
}

Expand All @@ -186,9 +186,9 @@ template <typename... Args> struct constructor {
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)

if (Py_TYPE(v_h.inst) == cl_type->type)
construct<Class>(v_h, new Cpp<Class>(std::forward<Args>(args)...), false);
construct<Class>(v_h, new Cpp<Class>{std::forward<Args>(args)...}, false);
else
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
construct<Class>(v_h, new Alias<Class>{std::forward<Args>(args)...}, true);
}, extra...);
}

Expand All @@ -200,7 +200,7 @@ template <typename... Args> struct constructor {
cl.def("__init__", [cl_type](handle self_, Args... args) {
auto v_h = load_v_h(self_, cl_type);
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
construct<Class>(v_h, new Alias<Class>{std::forward<Args>(args)...}, true);
}, extra...);
}
};
Expand All @@ -214,7 +214,7 @@ template <typename... Args> struct alias_constructor {
cl.def("__init__", [cl_type](handle self_, Args... args) {
auto v_h = load_v_h(self_, cl_type);
if (v_h.instance_registered()) return; // Ignore duplicate __init__ calls (see above)
construct<Class>(v_h, new Alias<Class>(std::forward<Args>(args)...), true);
construct<Class>(v_h, new Alias<Class>{std::forward<Args>(args)...}, true);
}, extra...);
}
};
Expand Down
11 changes: 11 additions & 0 deletions tests/test_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -280,6 +280,17 @@ TEST_SUBMODULE(class_, m) {
#else
.def("foo", static_cast<int (ProtectedB::*)() const>(&PublicistB::foo));
#endif

// test_brace_initialization
struct BraceInitialization {
int field1;
std::string field2;
};

py::class_<BraceInitialization>(m, "BraceInitialization")
.def(py::init<int, const std::string &>())
.def_readwrite("field1", &BraceInitialization::field1)
.def_readwrite("field2", &BraceInitialization::field2);
}

template <int N> class BreaksBase { public: virtual ~BreaksBase() = default; };
Expand Down
7 changes: 7 additions & 0 deletions tests/test_class.py
Original file line number Diff line number Diff line change
Expand Up @@ -195,3 +195,10 @@ def foo(self):

c = C()
assert c.foo() == 0


def test_brace_initialization():
""" Tests that simple POD classes can be constructed using C++11 brace initialization """
a = m.BraceInitialization(123, "test")
assert a.field1 == 123
assert a.field2 == "test"