Skip to content
Merged
Changes from 1 commit
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
2 changes: 1 addition & 1 deletion tests/test_class.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ TEST_SUBMODULE(class_, m) {
.def_static("new_instance", &NoConstructor::new_instance, "Return an instance");

py::class_<NoConstructorNew>(m, "NoConstructorNew")
.def(py::init([](const NoConstructorNew &self) { return self; })) // Need a NOOP __init__
.def(py::init<>()) // Need a NOOP __init__
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is the usual default ctor. Is this still the NOOP __init__ you wanted in #3265 (and is suggested by the NoConstructor... name)?

I don't fully understand the mechanics behind __new__ and __init__, so I resorted to limited trial and error. Long story short, this works:

.def(py::init([]() { return nullptr; }))

It passes with asan, msan, ubsan (Google toolchain).

FWIW

Before I settled on the above I tried:

.def("__init__", [](NoConstructorNew *) {}) // Need a NOOP __init__

This runs if NDEBUG is defined, but not otherwise (using Python 3.10):

assert.h assertion failed at third_party/python_runtime/v3_10/Objects/typeobject.c:3823 in PyObject *_PyType_Lookup(PyTypeObject *, PyObject *): !PyErr_Occurred()

On Sep 20, 2021 @Skylion007 wrote:

I also wish there was a cleaner way to not have to implement init if new is defined, but I can't figure out how to hack to the metaclass not to complain.

I confirmed that we need one of the __init__ alternatives to avoid this error:

>       instance = m.NoConstructorNew()  # .__new__(m.NoConstructor.__class__)
E       TypeError: pybind11_tests.class_.NoConstructorNew: No constructor defined!

I also confirmed that the existing code and the usual default ctor don't leak memory.

I'm really unsure TBH what's "right", strictly speaking, but the suggested code is the alternative that seems closest to an actual NOOP.

For completeness:

With the suggested code, this passes:

assert m.NoConstructorNew.__init__.__doc__.strip() == "__init__(self: pybind11_tests.class_.NoConstructorNew) -> None"

But l think it's best not to add this assert. This is just to report what the new __doc__ is, because IIUC the existing __doc__ led @XuehaiPan to suspect there is a bug.

.def_static("__new__",
[](const py::object &) { return NoConstructorNew::new_instance(); });

Expand Down