fix: bind noexcept and ref-qualified methods from unregistered base classes (#5992)

* Strip noexcept from cpp17 function type bindings

* Fix a bug and increase test coverage

* Does this fix it?

* Silence clang-tidy issue

* Simplify method adapter with macro and add missing rvalue adaptors + tests

* Supress clang-tidy errors

* Improve test coverage

* Add additional static assert

* Try to resolve MSVC C4003 warning

* Simplify method adaptor into 2 template instatiations with enable_if_t

* Fix ambiguous STL template

* Close remaining qualifier consistency gaps for member pointer bindings.

A production-code review after #2234 showed that ref-qualified member pointers were still inconsistently handled across def_buffer, vectorize, and overload_cast, so this adds the missing overloads with focused tests for each newly-supported signature.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Clarify why def_buffer/vectorize omit rvalue-qualified overloads.

These comments were added while reviewing the qualifier coverage follow-up, to document that buffer/vectorized calls operate on existing Python-owned instances and should not move-from self.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add compile-only overload_cast guard for ref-qualified methods.

This was added as a maintenance follow-up to the qualifier-consistency work, so future changes that introduce overload_cast ambiguity or wrong ref/noexcept resolution fail at compile time.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Refactor overload_cast_impl qualifier overloads with a macro.

As part of the qualifier-consistency maintenance follow-up, this reduces duplication in overload_cast_impl while preserving the same ref/noexcept coverage and keeping pedantic-clean macro expansion.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Expose __cpp_noexcept_function_type to Python tests and use explicit skip guards.

This replaces hasattr-based optional assertions with skipif-gated noexcept-only tests so skipped coverage is visible in pytest output while keeping non-noexcept checks always active.

Co-authored-by: Cursor <cursoragent@cursor.com>

* Add static_assert in method_adaptor to guard that T is a member function pointer.

Suggested by @Skylion007 in PR #5992 review comment [T007].

Made-with: Cursor

* automatic clang-format change (because of #6002)

---------

Co-authored-by: Ralf W. Grosse-Kunstleve <rgrossekunst@nvidia.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
Aaron Gokaslan
2026-03-26 19:21:32 -04:00
committed by Ralf W. Grosse-Kunstleve
parent c0cfa96555
commit 3cb5a763c1
10 changed files with 1015 additions and 21 deletions

View File

@@ -78,6 +78,27 @@ TEST_SUBMODULE(numpy_vectorize, m) {
struct VectorizeTestClass {
explicit VectorizeTestClass(int v) : value{v} {};
float method(int x, float y) const { return y + (float) (x + value); }
// Exercises vectorize(Return (Class::*)(Args...) &)
// NOLINTNEXTLINE(readability-make-member-function-const)
float method_lref(int x, float y) & { return y + (float) (x + value); }
// Exercises vectorize(Return (Class::*)(Args...) const &)
float method_const_lref(int x, float y) const & { return y + (float) (x + value); }
// Exercises vectorize(Return (Class::*)(Args...) noexcept)
// NOLINTNEXTLINE(readability-make-member-function-const)
float method_noexcept(int x, float y) noexcept { return y + (float) (x + value); }
// Exercises vectorize(Return (Class::*)(Args...) const noexcept)
float method_const_noexcept(int x, float y) const noexcept {
return y + (float) (x + value);
}
#ifdef __cpp_noexcept_function_type
// Exercises vectorize(Return (Class::*)(Args...) & noexcept)
// NOLINTNEXTLINE(readability-make-member-function-const)
float method_lref_noexcept(int x, float y) & noexcept { return y + (float) (x + value); }
// Exercises vectorize(Return (Class::*)(Args...) const & noexcept)
float method_const_lref_noexcept(int x, float y) const & noexcept {
return y + (float) (x + value);
}
#endif
int value = 0;
};
py::class_<VectorizeTestClass> vtc(m, "VectorizeTestClass");
@@ -85,6 +106,15 @@ TEST_SUBMODULE(numpy_vectorize, m) {
// Automatic vectorizing of methods
vtc.def("method", py::vectorize(&VectorizeTestClass::method));
vtc.def("method_lref", py::vectorize(&VectorizeTestClass::method_lref));
vtc.def("method_const_lref", py::vectorize(&VectorizeTestClass::method_const_lref));
vtc.def("method_noexcept", py::vectorize(&VectorizeTestClass::method_noexcept));
vtc.def("method_const_noexcept", py::vectorize(&VectorizeTestClass::method_const_noexcept));
#ifdef __cpp_noexcept_function_type
vtc.def("method_lref_noexcept", py::vectorize(&VectorizeTestClass::method_lref_noexcept));
vtc.def("method_const_lref_noexcept",
py::vectorize(&VectorizeTestClass::method_const_lref_noexcept));
#endif
// test_trivial_broadcasting
// Internal optimization test for whether the input is trivially broadcastable: