Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
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
50 changes: 48 additions & 2 deletions include/pybind11/detail/common.h
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,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<
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Should I normalize it here for all pybind11? Or keep it as utility that and create a new alias that always strips the noexcept

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

on the flip side we could use the noexcept tag handling to optimize away exception handling for certain edge cases like def_buffer maybe?

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
Expand Down Expand Up @@ -1212,6 +1228,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)

Expand Down
82 changes: 82 additions & 0 deletions include/pybind11/numpy.h
Original file line number Diff line number Diff line change
Expand Up @@ -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)
215 changes: 198 additions & 17 deletions include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -370,6 +370,96 @@ class cpp_function : public function {
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__"); }

Expand Down Expand Up @@ -1880,29 +1970,86 @@ inline void add_class_method(object &cls, const char *name_, const cpp_function
}
}

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)) {
return std::forward<F>(f);
}

/// 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>
auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(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<Class, Derived>::value,
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;
}

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;
PYBIND11_NAMESPACE_END(detail)

/// Given a pointer to a member function, cast it to its `Derived` version.
/// 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 T,
typename Adapted = typename detail::rebind_member_ptr<Derived, T>::type>
constexpr Adapted method_adaptor(T pmf) {
return detail::adapt_member_ptr<Derived>(pmf);
}

PYBIND11_NAMESPACE_BEGIN(detail)
Expand Down Expand Up @@ -2361,6 +2508,40 @@ class class_ : public detail::generic_type {
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,
Expand Down
Loading
Loading