mirror of
https://github.com/pybind/pybind11.git
synced 2026-05-25 15:24:52 +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:
committed by
Ralf W. Grosse-Kunstleve
parent
c0cfa96555
commit
3cb5a763c1
@@ -1057,14 +1057,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
|
||||
@@ -1213,6 +1229,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)
|
||||
|
||||
@@ -369,6 +369,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__"); }
|
||||
|
||||
@@ -1874,29 +1964,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)
|
||||
@@ -2355,6 +2505,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,
|
||||
|
||||
Reference in New Issue
Block a user