Files
pybind11/tests/test_numpy_vectorize.cpp
Aaron Gokaslan 3cb5a763c1 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>
2026-03-29 23:08:29 -07:00

138 lines
6.2 KiB
C++

/*
tests/test_numpy_vectorize.cpp -- auto-vectorize functions over NumPy array
arguments
Copyright (c) 2016 Wenzel Jakob <wenzel.jakob@epfl.ch>
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/numpy.h>
#include "pybind11_tests.h"
#include <utility>
double my_func(int x, float y, double z) {
py::print("my_func(x:int={}, y:float={:.0f}, z:float={:.0f})"_s.format(x, y, z));
return (float) x * y * z;
}
TEST_SUBMODULE(numpy_vectorize, m) {
try {
py::module_::import("numpy");
} catch (const py::error_already_set &) {
return;
}
// test_vectorize, test_docs, test_array_collapse
// Vectorize all arguments of a function (though non-vector arguments are also allowed)
m.def("vectorized_func", py::vectorize(my_func));
// Vectorize a lambda function with a capture object (e.g. to exclude some arguments from the
// vectorization)
m.def("vectorized_func2", [](py::array_t<int> x, py::array_t<float> y, float z) {
return py::vectorize([z](int x, float y) { return my_func(x, y, z); })(std::move(x),
std::move(y));
});
// Vectorize a complex-valued function
m.def("vectorized_func3",
py::vectorize([](std::complex<double> c) { return c * std::complex<double>(2.f); }));
// test_type_selection
// NumPy function which only accepts specific data types
// A lot of these no lints could be replaced with const refs, and probably should at some
// point.
m.def("selective_func",
[](const py::array_t<int, py::array::c_style> &) { return "Int branch taken."; });
m.def("selective_func",
[](const py::array_t<float, py::array::c_style> &) { return "Float branch taken."; });
m.def("selective_func", [](const py::array_t<std::complex<float>, py::array::c_style> &) {
return "Complex float branch taken.";
});
// test_passthrough_arguments
// Passthrough test: references and non-pod types should be automatically passed through (in
// the function definition below, only `b`, `d`, and `g` are vectorized):
struct NonPODClass {
explicit NonPODClass(int v) : value{v} {}
int value;
};
py::class_<NonPODClass>(m, "NonPODClass")
.def(py::init<int>())
.def_readwrite("value", &NonPODClass::value);
m.def("vec_passthrough",
py::vectorize([](const double *a,
double b,
// Changing this broke things
// NOLINTNEXTLINE(performance-unnecessary-value-param)
py::array_t<double> c,
const int &d,
int &e,
NonPODClass f,
const double g) { return *a + b + c.at(0) + d + e + f.value + g; }));
// test_method_vectorization
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");
vtc.def(py::init<int>()).def_readwrite("value", &VectorizeTestClass::value);
// 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:
py::enum_<py::detail::broadcast_trivial>(m, "trivial")
.value("f_trivial", py::detail::broadcast_trivial::f_trivial)
.value("c_trivial", py::detail::broadcast_trivial::c_trivial)
.value("non_trivial", py::detail::broadcast_trivial::non_trivial);
m.def("vectorized_is_trivial",
[](const py::array_t<int, py::array::forcecast> &arg1,
const py::array_t<float, py::array::forcecast> &arg2,
const py::array_t<double, py::array::forcecast> &arg3) {
py::ssize_t ndim = 0;
std::vector<py::ssize_t> shape;
std::array<py::buffer_info, 3> buffers{
{arg1.request(), arg2.request(), arg3.request()}};
return py::detail::broadcast(buffers, ndim, shape);
});
m.def("add_to", py::vectorize([](NonPODClass &x, int a) { x.value += a; }));
}