mirror of
https://github.com/pybind/pybind11.git
synced 2026-04-19 22:39:09 +00:00
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:
@@ -1065,14 +1065,30 @@ struct strip_function_object {
|
||||
using type = typename remove_class<decltype(&F::operator())>::type;
|
||||
};
|
||||
|
||||
// Strip noexcept from a free function type (C++17: noexcept is part of the type).
|
||||
template <typename T>
|
||||
struct remove_noexcept {
|
||||
using type = T;
|
||||
};
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename R, typename... A>
|
||||
struct remove_noexcept<R(A...) noexcept> {
|
||||
using type = R(A...);
|
||||
};
|
||||
#endif
|
||||
template <typename T>
|
||||
using remove_noexcept_t = typename remove_noexcept<T>::type;
|
||||
|
||||
// Extracts the function signature from a function, function pointer or lambda.
|
||||
// Strips noexcept from the result so that factory/pickle_factory partial specializations
|
||||
// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
|
||||
template <typename Function, typename F = remove_reference_t<Function>>
|
||||
using function_signature_t = conditional_t<
|
||||
using function_signature_t = remove_noexcept_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>;
|
||||
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
|
||||
@@ -1221,6 +1237,36 @@ struct overload_cast_impl {
|
||||
-> decltype(pmf) {
|
||||
return pmf;
|
||||
}
|
||||
|
||||
// Define const/non-const member-pointer selector pairs for qualifier combinations.
|
||||
// The `qualifiers` parameter is used in type position, where extra parentheses are invalid.
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define PYBIND11_OVERLOAD_CAST_MEMBER_PTR(qualifiers) \
|
||||
template <typename Return, typename Class> \
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) qualifiers, std::false_type = {}) \
|
||||
const noexcept -> decltype(pmf) { \
|
||||
return pmf; \
|
||||
} \
|
||||
template <typename Return, typename Class> \
|
||||
constexpr auto operator()(Return (Class::*pmf)(Args...) const qualifiers, std::true_type) \
|
||||
const noexcept -> decltype(pmf) { \
|
||||
return pmf; \
|
||||
}
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&&)
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename Return>
|
||||
constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
|
||||
return pf;
|
||||
}
|
||||
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(noexcept)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(& noexcept)
|
||||
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&& noexcept)
|
||||
#endif
|
||||
#undef PYBIND11_OVERLOAD_CAST_MEMBER_PTR
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
};
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
|
||||
@@ -2327,4 +2327,86 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Intentionally no &&/const&& overloads: vectorized method calls operate on the bound Python
|
||||
// instance and should not consume/move-from self.
|
||||
// Vectorize a class method (non-const, lvalue ref-qualified):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) &>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) &) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, lvalue ref-qualified):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const &>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const &) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// Vectorize a class method (non-const, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (non-const, lvalue ref-qualified, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) & noexcept>())),
|
||||
Return,
|
||||
Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) & noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
|
||||
// Vectorize a class method (const, lvalue ref-qualified, noexcept):
|
||||
template <typename Return,
|
||||
typename Class,
|
||||
typename... Args,
|
||||
typename Helper = detail::vectorize_helper<
|
||||
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const & noexcept>())),
|
||||
Return,
|
||||
const Class *,
|
||||
Args...>>
|
||||
Helper vectorize(Return (Class::*f)(Args...) const & noexcept) {
|
||||
return Helper(std::mem_fn(f));
|
||||
}
|
||||
#endif
|
||||
|
||||
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)
|
||||
|
||||
@@ -370,6 +370,96 @@ public:
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) &&, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const &&, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](const Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
/// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, lvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) & noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, no ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const noexcept, const Extra &...extra) {
|
||||
initialize([f](const Class *c,
|
||||
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, lvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const & noexcept, const Extra &...extra) {
|
||||
initialize([f](const Class *c,
|
||||
Arg... args) -> Return { return (c->*f)(std::forward<Arg>(args)...); },
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) && noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
|
||||
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier, noexcept)
|
||||
template <typename Return, typename Class, typename... Arg, typename... Extra>
|
||||
// NOLINTNEXTLINE(google-explicit-constructor)
|
||||
cpp_function(Return (Class::*f)(Arg...) const && noexcept, const Extra &...extra) {
|
||||
initialize(
|
||||
[f](const Class *c, Arg... args) -> Return {
|
||||
return (std::move(*c).*f)(std::forward<Arg>(args)...);
|
||||
},
|
||||
(Return (*)(const Class *, Arg...)) nullptr,
|
||||
extra...);
|
||||
}
|
||||
#endif
|
||||
|
||||
/// Return the function name
|
||||
object name() const { return attr("__name__"); }
|
||||
|
||||
@@ -1895,29 +1985,89 @@ inline void add_class_method(object &cls, const char *name_, const cpp_function
|
||||
}
|
||||
}
|
||||
|
||||
/// Type trait to rebind a member function pointer's class to `Derived`, preserving all
|
||||
/// cv/ref/noexcept qualifiers. The primary template has no `type` member, providing SFINAE
|
||||
/// failure for unsupported member function pointer types. `source_class` holds the original
|
||||
/// class for use in `is_accessible_base_of` checks.
|
||||
template <typename Derived, typename T>
|
||||
struct rebind_member_ptr {};
|
||||
|
||||
// Define one specialization per supported qualifier combination via a local macro.
|
||||
// The qualifiers argument appears in type position, not expression position, so
|
||||
// parenthesizing it would produce invalid C++.
|
||||
// The no-qualifier specialization is written out explicitly to avoid invoking the macro with an
|
||||
// empty argument, which triggers MSVC warning C4003.
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
struct rebind_member_ptr<Derived, Return (Class::*)(Args...)> {
|
||||
using type = Return (Derived::*)(Args...);
|
||||
using source_class = Class;
|
||||
};
|
||||
// NOLINTBEGIN(bugprone-macro-parentheses)
|
||||
#define PYBIND11_REBIND_MEMBER_PTR(qualifiers) \
|
||||
template <typename Derived, typename Return, typename Class, typename... Args> \
|
||||
struct rebind_member_ptr<Derived, Return (Class::*)(Args...) qualifiers> { \
|
||||
using type = Return (Derived::*)(Args...) qualifiers; \
|
||||
using source_class = Class; \
|
||||
}
|
||||
PYBIND11_REBIND_MEMBER_PTR(const);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const &);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&&);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const &&);
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
PYBIND11_REBIND_MEMBER_PTR(noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(& noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const & noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(&& noexcept);
|
||||
PYBIND11_REBIND_MEMBER_PTR(const && noexcept);
|
||||
#endif
|
||||
#undef PYBIND11_REBIND_MEMBER_PTR
|
||||
// NOLINTEND(bugprone-macro-parentheses)
|
||||
|
||||
/// Shared implementation body for all method_adaptor member-function-pointer overloads.
|
||||
/// Asserts Base is accessible from Derived, then casts the member pointer.
|
||||
template <typename Derived,
|
||||
typename T,
|
||||
typename Traits = rebind_member_ptr<Derived, T>,
|
||||
typename Adapted = typename Traits::type>
|
||||
constexpr PYBIND11_ALWAYS_INLINE Adapted adapt_member_ptr(T pmf) {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<typename Traits::source_class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_END(detail)
|
||||
|
||||
/// Given a pointer to a member function, cast it to its `Derived` version.
|
||||
/// Forward everything else unchanged.
|
||||
template <typename /*Derived*/, typename F>
|
||||
auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
|
||||
/// For all other callables (lambdas, function pointers, etc.), forward unchanged.
|
||||
///
|
||||
/// Two overloads cover all cases without explicit per-qualifier instantiations:
|
||||
///
|
||||
/// (1) Generic fallback — disabled for member function pointers so that (2) wins
|
||||
/// without any partial-ordering ambiguity.
|
||||
/// (2) MFP overload — SFINAE on rebind_member_ptr::type, which exists for every
|
||||
/// supported qualifier combination (const, &, &&, noexcept, ...). A single
|
||||
/// template therefore covers all combinations that rebind_member_ptr handles.
|
||||
template <
|
||||
typename /*Derived*/,
|
||||
typename F,
|
||||
detail::enable_if_t<!std::is_member_function_pointer<detail::remove_reference_t<F>>::value,
|
||||
int> = 0>
|
||||
constexpr auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
|
||||
return std::forward<F>(f);
|
||||
}
|
||||
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(Args...) {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<Class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
}
|
||||
|
||||
template <typename Derived, typename Return, typename Class, typename... Args>
|
||||
auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(Args...) const {
|
||||
static_assert(
|
||||
detail::is_accessible_base_of<Class, Derived>::value,
|
||||
"Cannot bind an inaccessible base class method; use a lambda definition instead");
|
||||
return pmf;
|
||||
template <typename Derived,
|
||||
typename T,
|
||||
typename Adapted = typename detail::rebind_member_ptr<Derived, T>::type>
|
||||
constexpr Adapted method_adaptor(T pmf) {
|
||||
// Expected to be redundant (SFINAE on rebind_member_ptr) but cheap and makes the intent
|
||||
// explicit.
|
||||
static_assert(std::is_member_function_pointer<T>::value,
|
||||
"method_adaptor: T must be a member function pointer");
|
||||
return detail::adapt_member_ptr<Derived>(pmf);
|
||||
}
|
||||
|
||||
PYBIND11_NAMESPACE_BEGIN(detail)
|
||||
@@ -2376,6 +2526,40 @@ public:
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
// Intentionally no &&/const&& overloads: buffer protocol callbacks are invoked on an
|
||||
// existing Python object and should not move-from self.
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) &) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const &) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) noexcept) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const noexcept) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) & noexcept) {
|
||||
return def_buffer([func](type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
|
||||
template <typename Return, typename Class, typename... Args>
|
||||
class_ &def_buffer(Return (Class::*func)(Args...) const & noexcept) {
|
||||
return def_buffer([func](const type &obj) { return (obj.*func)(); });
|
||||
}
|
||||
#endif
|
||||
|
||||
template <typename C, typename D, typename... Extra>
|
||||
class_ &def_readwrite(const char *name, D C::*pm, const Extra &...extra) {
|
||||
static_assert(std::is_same<C, type>::value || std::is_base_of<C, type>::value,
|
||||
|
||||
@@ -111,6 +111,12 @@ PYBIND11_MODULE(pybind11_tests, m, py::mod_gil_not_used()) {
|
||||
m.attr("detailed_error_messages_enabled") = false;
|
||||
#endif
|
||||
|
||||
#if defined(__cpp_noexcept_function_type)
|
||||
m.attr("defined___cpp_noexcept_function_type") = true;
|
||||
#else
|
||||
m.attr("defined___cpp_noexcept_function_type") = false;
|
||||
#endif
|
||||
|
||||
py::class_<UserType>(m, "UserType", "A `py::class_` type for testing")
|
||||
.def(py::init<>())
|
||||
.def(py::init<int>())
|
||||
|
||||
@@ -439,4 +439,133 @@ TEST_SUBMODULE(buffers, m) {
|
||||
PyBuffer_Release(&buffer);
|
||||
return result;
|
||||
});
|
||||
|
||||
// test_noexcept_def_buffer (issue #2234)
|
||||
// def_buffer(Return (Class::*)(Args...) noexcept) and
|
||||
// def_buffer(Return (Class::*)(Args...) const noexcept) must compile and work correctly.
|
||||
struct OneDBuffer {
|
||||
// Declare m_data before m_n to match initialiser list order below.
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBuffer(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBuffer() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) noexcept)
|
||||
py::buffer_info get_buffer() noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
|
||||
// non-const noexcept member function form
|
||||
py::class_<OneDBuffer>(m, "OneDBuffer", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBuffer::get_buffer);
|
||||
|
||||
// const noexcept member function form (separate class to avoid ambiguity)
|
||||
struct OneDBufferConst {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConst(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConst() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const noexcept)
|
||||
py::buffer_info get_buffer() const noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConst>(m, "OneDBufferConst", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConst::get_buffer);
|
||||
|
||||
// test_ref_qualified_def_buffer
|
||||
struct OneDBufferLRef {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferLRef() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) &)
|
||||
py::buffer_info get_buffer() & {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferLRef>(m, "OneDBufferLRef", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferLRef::get_buffer);
|
||||
|
||||
struct OneDBufferConstLRef {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConstLRef(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConstLRef() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const &)
|
||||
py::buffer_info get_buffer() const & {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConstLRef>(m, "OneDBufferConstLRef", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConstLRef::get_buffer);
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
struct OneDBufferLRefNoexcept {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferLRefNoexcept(py::ssize_t n) : m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferLRefNoexcept() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
py::buffer_info get_buffer() & noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)});
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferLRefNoexcept>(m, "OneDBufferLRefNoexcept", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferLRefNoexcept::get_buffer);
|
||||
|
||||
struct OneDBufferConstLRefNoexcept {
|
||||
float *m_data;
|
||||
py::ssize_t m_n;
|
||||
explicit OneDBufferConstLRefNoexcept(py::ssize_t n)
|
||||
: m_data(new float[(size_t) n]()), m_n(n) {}
|
||||
~OneDBufferConstLRefNoexcept() { delete[] m_data; }
|
||||
// Exercises def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
py::buffer_info get_buffer() const & noexcept {
|
||||
return py::buffer_info(m_data,
|
||||
sizeof(float),
|
||||
py::format_descriptor<float>::format(),
|
||||
1,
|
||||
{m_n},
|
||||
{(py::ssize_t) sizeof(float)},
|
||||
/*readonly=*/true);
|
||||
}
|
||||
};
|
||||
py::class_<OneDBufferConstLRefNoexcept>(
|
||||
m, "OneDBufferConstLRefNoexcept", py::buffer_protocol())
|
||||
.def(py::init<py::ssize_t>())
|
||||
.def_buffer(&OneDBufferConstLRefNoexcept::get_buffer);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ import struct
|
||||
import pytest
|
||||
|
||||
import env
|
||||
from pybind11_tests import ConstructorStats
|
||||
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import buffers as m
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
@@ -399,3 +399,73 @@ def test_to_pybuffer_contiguity(type):
|
||||
m.get_py_buffer(dmat, m.PyBUF_ANY_CONTIGUOUS)
|
||||
with pytest.raises(expected_exception):
|
||||
m.get_py_buffer(dmat, m.PyBUF_F_CONTIGUOUS)
|
||||
|
||||
|
||||
def test_noexcept_def_buffer():
|
||||
"""Test issue #2234: def_buffer with noexcept member function pointers.
|
||||
|
||||
Covers both new def_buffer specialisations:
|
||||
- def_buffer(Return (Class::*)(Args...) noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const noexcept)
|
||||
"""
|
||||
# non-const noexcept member function form
|
||||
buf = m.OneDBuffer(5)
|
||||
arr = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr.shape == (5,)
|
||||
arr[2] = 3.14
|
||||
arr2 = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr2[2] == pytest.approx(3.14)
|
||||
|
||||
# const noexcept member function form
|
||||
cbuf = m.OneDBufferConst(4)
|
||||
carr = np.frombuffer(cbuf, dtype=np.float32)
|
||||
assert carr.shape == (4,)
|
||||
assert carr.flags["WRITEABLE"] is False
|
||||
|
||||
|
||||
def test_ref_qualified_def_buffer():
|
||||
"""Test issue #2234 follow-up: def_buffer with ref-qualified member function pointers.
|
||||
|
||||
Covers:
|
||||
- def_buffer(Return (Class::*)(Args...) &)
|
||||
- def_buffer(Return (Class::*)(Args...) const &)
|
||||
- def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
# non-const lvalue ref-qualified member function form
|
||||
buf = m.OneDBufferLRef(5)
|
||||
arr = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr.shape == (5,)
|
||||
arr[1] = 2.5
|
||||
arr2 = np.frombuffer(buf, dtype=np.float32)
|
||||
assert arr2[1] == pytest.approx(2.5)
|
||||
|
||||
# const lvalue ref-qualified member function form
|
||||
cbuf = m.OneDBufferConstLRef(4)
|
||||
carr = np.frombuffer(cbuf, dtype=np.float32)
|
||||
assert carr.shape == (4,)
|
||||
assert carr.flags["WRITEABLE"] is False
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_ref_qualified_noexcept_def_buffer():
|
||||
"""Test issue #2234 follow-up: def_buffer with noexcept ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- def_buffer(Return (Class::*)(Args...) & noexcept)
|
||||
- def_buffer(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
nbuf = m.OneDBufferLRefNoexcept(3)
|
||||
narr = np.frombuffer(nbuf, dtype=np.float32)
|
||||
assert narr.shape == (3,)
|
||||
narr[2] = 7.0
|
||||
narr2 = np.frombuffer(nbuf, dtype=np.float32)
|
||||
assert narr2[2] == pytest.approx(7.0)
|
||||
|
||||
ncbuf = m.OneDBufferConstLRefNoexcept(2)
|
||||
ncarr = np.frombuffer(ncbuf, dtype=np.float32)
|
||||
assert ncarr.shape == (2,)
|
||||
assert ncarr.flags["WRITEABLE"] is False
|
||||
|
||||
@@ -167,6 +167,123 @@ public:
|
||||
double sum() const { return rw_value + ro_value; }
|
||||
};
|
||||
|
||||
// Issue #2234: noexcept methods in an unregistered base should be bindable on the derived class.
|
||||
// In C++17, noexcept is part of the function type, so &Derived::method resolves to
|
||||
// a Base member function pointer with noexcept, requiring explicit template specializations.
|
||||
class NoexceptUnregisteredBase {
|
||||
public:
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const noexcept, ...)
|
||||
int value() const noexcept { return m_value; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) noexcept, ...)
|
||||
void set_value(int v) noexcept { m_value = v; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) & noexcept, ...)
|
||||
void increment() & noexcept { ++m_value; }
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
|
||||
int capped_value() const & noexcept { return m_value < 100 ? m_value : 100; }
|
||||
|
||||
private:
|
||||
int m_value = 99;
|
||||
};
|
||||
class NoexceptDerived : public NoexceptUnregisteredBase {
|
||||
public:
|
||||
using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
|
||||
};
|
||||
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...) and
|
||||
// cpp_function(Return (Class::*)(Args...) const &&, ...) via an unregistered base.
|
||||
class RValueRefUnregisteredBase {
|
||||
public:
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...).
|
||||
// Moves m_payload to verify that std::move(*c).*f is used in the lambda body.
|
||||
std::string take() && { // NOLINT(readability-make-member-function-const)
|
||||
return std::move(m_payload);
|
||||
}
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const &&, ...)
|
||||
int peek() const && { return m_value; }
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) && noexcept, ...)
|
||||
std::string take_noexcept() && noexcept { // NOLINT(readability-make-member-function-const)
|
||||
return std::move(m_payload);
|
||||
}
|
||||
// Exercises cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
|
||||
int peek_noexcept() const && noexcept { return m_value; }
|
||||
#endif
|
||||
|
||||
private:
|
||||
int m_value = 77;
|
||||
std::string m_payload{"rref_payload"};
|
||||
};
|
||||
class RValueRefDerived : public RValueRefUnregisteredBase {
|
||||
public:
|
||||
using RValueRefUnregisteredBase::RValueRefUnregisteredBase;
|
||||
};
|
||||
|
||||
// Exercises overload_cast with noexcept member function pointers (issue #2234).
|
||||
// In C++17, overload_cast must have noexcept variants to resolve noexcept overloads.
|
||||
struct NoexceptOverloaded {
|
||||
py::str method(int) noexcept { return "(int)"; }
|
||||
py::str method(int) const noexcept { return "(int) const"; }
|
||||
py::str method(float) noexcept { return "(float)"; }
|
||||
};
|
||||
// Exercises overload_cast with noexcept free function pointers.
|
||||
int noexcept_free_func(int x) noexcept { return x + 1; }
|
||||
int noexcept_free_func(float x) noexcept { return static_cast<int>(x) + 2; }
|
||||
|
||||
// Exercises overload_cast with ref-qualified member function pointers.
|
||||
struct RefQualifiedOverloaded {
|
||||
py::str method(int) & { return "(int) &"; }
|
||||
py::str method(int) const & { return "(int) const &"; }
|
||||
py::str method(float) && { return "(float) &&"; }
|
||||
py::str method(float) const && { return "(float) const &&"; }
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
py::str method(long) & noexcept { return "(long) & noexcept"; }
|
||||
py::str method(long) const & noexcept { return "(long) const & noexcept"; }
|
||||
py::str method(double) && noexcept { return "(double) && noexcept"; }
|
||||
py::str method(double) const && noexcept { return "(double) const && noexcept"; }
|
||||
#endif
|
||||
};
|
||||
|
||||
// Compile-only guard: catch overload_cast resolution regressions/ambiguities early.
|
||||
using RefQualifiedOverloadedIntCast = py::detail::overload_cast_impl<int>;
|
||||
using RefQualifiedOverloadedFloatCast = py::detail::overload_cast_impl<float>;
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedIntCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(int) &>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedIntCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(int) const &>::value,
|
||||
"");
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(float) &&>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedFloatCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(float) const &&>::value,
|
||||
"");
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
using RefQualifiedOverloadedLongCast = py::detail::overload_cast_impl<long>;
|
||||
using RefQualifiedOverloadedDoubleCast = py::detail::overload_cast_impl<double>;
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedLongCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(long) & noexcept>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedLongCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(long) const & noexcept>::value,
|
||||
"");
|
||||
static_assert(
|
||||
std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(&RefQualifiedOverloaded::method)),
|
||||
py::str (RefQualifiedOverloaded::*)(double) && noexcept>::value,
|
||||
"");
|
||||
static_assert(std::is_same<decltype(RefQualifiedOverloadedDoubleCast{}(
|
||||
&RefQualifiedOverloaded::method, py::const_)),
|
||||
py::str (RefQualifiedOverloaded::*)(double) const && noexcept>::value,
|
||||
"");
|
||||
#endif
|
||||
|
||||
// Test explicit lvalue ref-qualification
|
||||
struct RefQualified {
|
||||
int value = 0;
|
||||
@@ -498,6 +615,161 @@ TEST_SUBMODULE(methods_and_attributes, m) {
|
||||
= decltype(py::method_adaptor<RegisteredDerived>(&RegisteredDerived::do_nothing));
|
||||
static_assert(std::is_same<Adapted, void (RegisteredDerived::*)() const>::value, "");
|
||||
|
||||
// test_noexcept_base (issue #2234)
|
||||
// In C++17, noexcept is part of the function type. Binding a noexcept method from an
|
||||
// unregistered base class must resolve `self` to the derived type, not the base type.
|
||||
py::class_<NoexceptDerived>(m, "NoexceptDerived")
|
||||
.def(py::init<>())
|
||||
// cpp_function(Return (Class::*)(Args...) const noexcept, ...)
|
||||
.def("value", &NoexceptDerived::value)
|
||||
// cpp_function(Return (Class::*)(Args...) noexcept, ...)
|
||||
.def("set_value", &NoexceptDerived::set_value)
|
||||
// cpp_function(Return (Class::*)(Args...) & noexcept, ...)
|
||||
.def("increment", &NoexceptDerived::increment)
|
||||
// cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
|
||||
.def("capped_value", &NoexceptDerived::capped_value);
|
||||
|
||||
// test_rvalue_ref_qualified_methods: rvalue-ref-qualified methods from an unregistered base.
|
||||
// method_adaptor must rebind &&/const&& member pointers to the derived type.
|
||||
py::class_<RValueRefDerived>(m, "RValueRefDerived")
|
||||
.def(py::init<>())
|
||||
// cpp_function(Return (Class::*)(Args...) &&, ...)
|
||||
.def("take", &RValueRefDerived::take)
|
||||
// cpp_function(Return (Class::*)(Args...) const &&, ...)
|
||||
.def("peek", &RValueRefDerived::peek)
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// cpp_function(Return (Class::*)(Args...) && noexcept, ...)
|
||||
.def("take_noexcept", &RValueRefDerived::take_noexcept)
|
||||
// cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
|
||||
.def("peek_noexcept", &RValueRefDerived::peek_noexcept)
|
||||
#endif
|
||||
;
|
||||
|
||||
// Verify that &&-qualified methods cannot be called on lvalues, only on rvalues.
|
||||
// This confirms that the cpp_function lambda must use std::move(*c).*f, not c->*f.
|
||||
#if __cplusplus >= 201703L
|
||||
static_assert(!std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
|
||||
RValueRefUnregisteredBase &>::value,
|
||||
"&&-qualified method must not be callable on lvalue");
|
||||
static_assert(std::is_invocable<std::string (RValueRefUnregisteredBase::*)() &&,
|
||||
RValueRefUnregisteredBase &&>::value,
|
||||
"&&-qualified method must be callable on rvalue");
|
||||
#endif
|
||||
|
||||
// Verify method_adaptor preserves &&/const&& qualifiers when rebinding.
|
||||
using AdaptedRRef = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take));
|
||||
static_assert(std::is_same<AdaptedRRef, std::string (RValueRefDerived::*)() &&>::value, "");
|
||||
using AdaptedConstRRef
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek));
|
||||
static_assert(std::is_same<AdaptedConstRRef, int (RValueRefDerived::*)() const &&>::value, "");
|
||||
|
||||
#ifdef __cpp_noexcept_function_type
|
||||
// method_adaptor must also handle noexcept member function pointers (issue #2234).
|
||||
// Verify the noexcept specifier is preserved in the resulting Derived pointer type.
|
||||
using AdaptedConstNoexcept
|
||||
= decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::value));
|
||||
static_assert(
|
||||
std::is_same<AdaptedConstNoexcept, int (NoexceptDerived::*)() const noexcept>::value, "");
|
||||
using AdaptedNoexcept
|
||||
= decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::set_value));
|
||||
static_assert(std::is_same<AdaptedNoexcept, void (NoexceptDerived::*)(int) noexcept>::value,
|
||||
"");
|
||||
using AdaptedRRefNoexcept
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take_noexcept));
|
||||
static_assert(std::is_same < AdaptedRRefNoexcept,
|
||||
std::string (RValueRefDerived::*)() && noexcept > ::value,
|
||||
"");
|
||||
using AdaptedConstRRefNoexcept
|
||||
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek_noexcept));
|
||||
static_assert(std::is_same < AdaptedConstRRefNoexcept,
|
||||
int (RValueRefDerived::*)() const && noexcept > ::value,
|
||||
"");
|
||||
#endif
|
||||
|
||||
// test_noexcept_overload_cast (issue #2234)
|
||||
// overload_cast must have noexcept operator() overloads so it can resolve noexcept methods.
|
||||
#ifdef PYBIND11_OVERLOAD_CAST
|
||||
py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
|
||||
.def(py::init<>())
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
|
||||
.def("method", py::overload_cast<int>(&NoexceptOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
|
||||
.def("method_const", py::overload_cast<int>(&NoexceptOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
|
||||
.def("method_float", py::overload_cast<float>(&NoexceptOverloaded::method));
|
||||
// overload_cast_impl::operator()(Return (*)(Args...) noexcept)
|
||||
m.def("noexcept_free_func", py::overload_cast<int>(noexcept_free_func));
|
||||
m.def("noexcept_free_func_float", py::overload_cast<float>(noexcept_free_func));
|
||||
|
||||
py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
|
||||
.def(py::init<>())
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
|
||||
.def("method_lref", py::overload_cast<int>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
|
||||
.def("method_const_lref",
|
||||
py::overload_cast<int>(&RefQualifiedOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
|
||||
.def("method_rref", py::overload_cast<float>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
|
||||
.def("method_const_rref",
|
||||
py::overload_cast<float>(&RefQualifiedOverloaded::method, py::const_))
|
||||
# ifdef __cpp_noexcept_function_type
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
.def("method_lref_noexcept", py::overload_cast<long>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
.def("method_const_lref_noexcept",
|
||||
py::overload_cast<long>(&RefQualifiedOverloaded::method, py::const_))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
.def("method_rref_noexcept", py::overload_cast<double>(&RefQualifiedOverloaded::method))
|
||||
// overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
.def("method_const_rref_noexcept",
|
||||
py::overload_cast<double>(&RefQualifiedOverloaded::method, py::const_))
|
||||
# endif
|
||||
;
|
||||
#else
|
||||
// Fallback using explicit static_cast for C++11/14
|
||||
py::class_<NoexceptOverloaded>(m, "NoexceptOverloaded")
|
||||
.def(py::init<>())
|
||||
.def("method",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(int)>(&NoexceptOverloaded::method))
|
||||
.def("method_const",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(int) const>(&NoexceptOverloaded::method))
|
||||
.def("method_float",
|
||||
static_cast<py::str (NoexceptOverloaded::*)(float)>(&NoexceptOverloaded::method));
|
||||
m.def("noexcept_free_func", static_cast<int (*)(int)>(noexcept_free_func));
|
||||
m.def("noexcept_free_func_float", static_cast<int (*)(float)>(noexcept_free_func));
|
||||
|
||||
py::class_<RefQualifiedOverloaded>(m, "RefQualifiedOverloaded")
|
||||
.def(py::init<>())
|
||||
.def("method_lref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(int) &>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_lref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(int) const &>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_rref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(float) &&>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_rref",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(float) const &&>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
# ifdef __cpp_noexcept_function_type
|
||||
.def("method_lref_noexcept",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(long) & noexcept>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_const_lref_noexcept",
|
||||
static_cast<py::str (RefQualifiedOverloaded::*)(long) const & noexcept>(
|
||||
&RefQualifiedOverloaded::method))
|
||||
.def("method_rref_noexcept",
|
||||
static_cast < py::str (RefQualifiedOverloaded::*)(double)
|
||||
&& noexcept > (&RefQualifiedOverloaded::method))
|
||||
.def("method_const_rref_noexcept",
|
||||
static_cast < py::str (RefQualifiedOverloaded::*)(double) const && noexcept
|
||||
> (&RefQualifiedOverloaded::method))
|
||||
# endif
|
||||
;
|
||||
#endif
|
||||
|
||||
// test_methods_and_attributes
|
||||
py::class_<RefQualified>(m, "RefQualified")
|
||||
.def(py::init<>())
|
||||
|
||||
@@ -5,7 +5,7 @@ import sys
|
||||
import pytest
|
||||
|
||||
import env
|
||||
from pybind11_tests import ConstructorStats
|
||||
from pybind11_tests import ConstructorStats, defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import methods_and_attributes as m
|
||||
|
||||
NO_GETTER_MSG = (
|
||||
@@ -534,6 +534,130 @@ def test_unregistered_base_implementations():
|
||||
assert a.ro_value_prop == 1.75
|
||||
|
||||
|
||||
def test_noexcept_base():
|
||||
"""Test issue #2234: binding noexcept methods inherited from an unregistered base class.
|
||||
|
||||
In C++17 noexcept is part of the function type, so &Derived::noexcept_method resolves
|
||||
to a Base member-function pointer with noexcept specifier. pybind11 must use the Derived
|
||||
type as `self`, not the Base type, otherwise the call raises TypeError at runtime.
|
||||
|
||||
Covers all four new cpp_function constructor specialisations:
|
||||
- Return (Class::*)(Args...) noexcept (set_value)
|
||||
- Return (Class::*)(Args...) const noexcept (value)
|
||||
- Return (Class::*)(Args...) & noexcept (increment)
|
||||
- Return (Class::*)(Args...) const & noexcept (capped_value)
|
||||
"""
|
||||
obj = m.NoexceptDerived()
|
||||
# const noexcept
|
||||
assert obj.value() == 99
|
||||
# noexcept (non-const)
|
||||
obj.set_value(7)
|
||||
assert obj.value() == 7
|
||||
# & noexcept (non-const lvalue ref-qualified)
|
||||
obj.increment()
|
||||
assert obj.value() == 8
|
||||
# const & noexcept (const lvalue ref-qualified)
|
||||
assert obj.capped_value() == 8
|
||||
obj.set_value(200)
|
||||
assert obj.capped_value() == 100 # capped at 100
|
||||
|
||||
|
||||
def test_rvalue_ref_qualified_methods():
|
||||
"""Test that rvalue-ref-qualified (&&/const&&) methods from an unregistered base bind
|
||||
correctly with `self` resolved to the derived type.
|
||||
|
||||
take() moves m_payload out on each call, so the second call returns "".
|
||||
This confirms that the cpp_function lambda uses std::move(*c).*f rather than c->*f.
|
||||
|
||||
Covers:
|
||||
- Return (Class::*)(Args...) && (take)
|
||||
- Return (Class::*)(Args...) const && (peek)
|
||||
"""
|
||||
obj = m.RValueRefDerived()
|
||||
# && moves m_payload: first call gets the value, second gets empty string
|
||||
assert obj.take() == "rref_payload"
|
||||
assert obj.take() == ""
|
||||
# const && doesn't move: peek() is stable across calls
|
||||
assert obj.peek() == 77
|
||||
assert obj.peek() == 77
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_rvalue_ref_qualified_methods():
|
||||
"""Test noexcept rvalue-ref-qualified methods from an unregistered base.
|
||||
|
||||
Covers:
|
||||
- Return (Class::*)(Args...) && noexcept (take_noexcept)
|
||||
- Return (Class::*)(Args...) const && noexcept (peek_noexcept)
|
||||
"""
|
||||
obj = m.RValueRefDerived()
|
||||
assert obj.take_noexcept() == "rref_payload"
|
||||
assert obj.take_noexcept() == ""
|
||||
assert obj.peek_noexcept() == 77
|
||||
assert obj.peek_noexcept() == 77
|
||||
|
||||
|
||||
def test_noexcept_overload_cast():
|
||||
"""Test issue #2234: overload_cast must handle noexcept member and free function pointers.
|
||||
|
||||
In C++17 noexcept is part of the function type, so overload_cast_impl needs dedicated
|
||||
operator() overloads for noexcept free functions and non-const/const member functions.
|
||||
"""
|
||||
obj = m.NoexceptOverloaded()
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type)
|
||||
assert obj.method(1) == "(int)"
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) const noexcept, true_type)
|
||||
assert obj.method_const(2) == "(int) const"
|
||||
# overload_cast_impl::operator()(Return (Class::*)(Args...) noexcept, false_type) float
|
||||
assert obj.method_float(3.0) == "(float)"
|
||||
# overload_cast_impl::operator()(Return (*)(Args...) noexcept)
|
||||
assert m.noexcept_free_func(10) == 11
|
||||
assert m.noexcept_free_func_float(10.0) == 12
|
||||
|
||||
|
||||
def test_ref_qualified_overload_cast():
|
||||
"""Test issue #2234 follow-up: overload_cast with ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) &, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const &, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) &&, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const &&, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
"""
|
||||
obj = m.RefQualifiedOverloaded()
|
||||
assert obj.method_lref(1) == "(int) &"
|
||||
assert obj.method_const_lref(1) == "(int) const &"
|
||||
assert obj.method_rref(1.0) == "(float) &&"
|
||||
assert obj.method_const_rref(1.0) == "(float) const &&"
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_ref_qualified_overload_cast():
|
||||
"""Test issue #2234 follow-up: overload_cast with noexcept ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) & noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const & noexcept, true_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) && noexcept, false_type)
|
||||
- overload_cast_impl::operator()(Return (Class::*)(Args...) const && noexcept, true_type)
|
||||
"""
|
||||
obj = m.RefQualifiedOverloaded()
|
||||
assert obj.method_lref_noexcept(1) == "(long) & noexcept"
|
||||
assert obj.method_const_lref_noexcept(1) == "(long) const & noexcept"
|
||||
assert obj.method_rref_noexcept(1.0) == "(double) && noexcept"
|
||||
assert obj.method_const_rref_noexcept(1.0) == "(double) const && noexcept"
|
||||
|
||||
|
||||
def test_ref_qualified():
|
||||
"""Tests that explicit lvalue ref-qualified methods can be called just like their
|
||||
non ref-qualified counterparts."""
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
|
||||
from pybind11_tests import defined___cpp_noexcept_function_type
|
||||
from pybind11_tests import numpy_vectorize as m
|
||||
|
||||
np = pytest.importorskip("numpy")
|
||||
@@ -246,6 +247,56 @@ def test_method_vectorization():
|
||||
assert np.all(o.method(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_ref_qualified_method_vectorization():
|
||||
"""Test issue #2234 follow-up: vectorize with lvalue-ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- vectorize(Return (Class::*)(Args...) &)
|
||||
- vectorize(Return (Class::*)(Args...) const &)
|
||||
- vectorize(Return (Class::*)(Args...) & noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
assert np.all(o.method_lref(x, y) == [[14, 15], [24, 25]])
|
||||
assert np.all(o.method_const_lref(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
@pytest.mark.skipif(
|
||||
not defined___cpp_noexcept_function_type,
|
||||
reason="Requires __cpp_noexcept_function_type",
|
||||
)
|
||||
def test_noexcept_ref_qualified_method_vectorization():
|
||||
"""Test issue #2234 follow-up: vectorize with noexcept lvalue-ref-qualified member pointers.
|
||||
|
||||
Covers:
|
||||
- vectorize(Return (Class::*)(Args...) & noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const & noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
assert np.all(o.method_lref_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
assert np.all(o.method_const_lref_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_noexcept_method_vectorization():
|
||||
"""Test issue #2234: vectorize must handle noexcept member function pointers.
|
||||
|
||||
Covers both new vectorize specialisations:
|
||||
- vectorize(Return (Class::*)(Args...) noexcept)
|
||||
- vectorize(Return (Class::*)(Args...) const noexcept)
|
||||
"""
|
||||
o = m.VectorizeTestClass(3)
|
||||
x = np.array([1, 2], dtype="int")
|
||||
y = np.array([[10], [20]], dtype="float32")
|
||||
# vectorize(Return (Class::*)(Args...) noexcept)
|
||||
assert np.all(o.method_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
# vectorize(Return (Class::*)(Args...) const noexcept)
|
||||
assert np.all(o.method_const_noexcept(x, y) == [[14, 15], [24, 25]])
|
||||
|
||||
|
||||
def test_array_collapse():
|
||||
assert not isinstance(m.vectorized_func(1, 2, 3), np.ndarray)
|
||||
assert not isinstance(m.vectorized_func(np.array(1), 2, 3), np.ndarray)
|
||||
|
||||
Reference in New Issue
Block a user