Skip to content

Commit c0bbd8b

Browse files
Skylion007rwgkcursoragent
authored
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>
1 parent e4fbc05 commit c0bbd8b

10 files changed

Lines changed: 1015 additions & 21 deletions

include/pybind11/detail/common.h

Lines changed: 48 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1065,14 +1065,30 @@ struct strip_function_object {
10651065
using type = typename remove_class<decltype(&F::operator())>::type;
10661066
};
10671067

1068+
// Strip noexcept from a free function type (C++17: noexcept is part of the type).
1069+
template <typename T>
1070+
struct remove_noexcept {
1071+
using type = T;
1072+
};
1073+
#ifdef __cpp_noexcept_function_type
1074+
template <typename R, typename... A>
1075+
struct remove_noexcept<R(A...) noexcept> {
1076+
using type = R(A...);
1077+
};
1078+
#endif
1079+
template <typename T>
1080+
using remove_noexcept_t = typename remove_noexcept<T>::type;
1081+
10681082
// Extracts the function signature from a function, function pointer or lambda.
1083+
// Strips noexcept from the result so that factory/pickle_factory partial specializations
1084+
// (which match plain Return(Args...)) work correctly with noexcept callables (issue #2234).
10691085
template <typename Function, typename F = remove_reference_t<Function>>
1070-
using function_signature_t = conditional_t<
1086+
using function_signature_t = remove_noexcept_t<conditional_t<
10711087
std::is_function<F>::value,
10721088
F,
10731089
typename conditional_t<std::is_pointer<F>::value || std::is_member_pointer<F>::value,
10741090
std::remove_pointer<F>,
1075-
strip_function_object<F>>::type>;
1091+
strip_function_object<F>>::type>>;
10761092

10771093
/// Returns true if the type looks like a lambda: that is, isn't a function, pointer or member
10781094
/// 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 {
12211237
-> decltype(pmf) {
12221238
return pmf;
12231239
}
1240+
1241+
// Define const/non-const member-pointer selector pairs for qualifier combinations.
1242+
// The `qualifiers` parameter is used in type position, where extra parentheses are invalid.
1243+
// NOLINTBEGIN(bugprone-macro-parentheses)
1244+
#define PYBIND11_OVERLOAD_CAST_MEMBER_PTR(qualifiers) \
1245+
template <typename Return, typename Class> \
1246+
constexpr auto operator()(Return (Class::*pmf)(Args...) qualifiers, std::false_type = {}) \
1247+
const noexcept -> decltype(pmf) { \
1248+
return pmf; \
1249+
} \
1250+
template <typename Return, typename Class> \
1251+
constexpr auto operator()(Return (Class::*pmf)(Args...) const qualifiers, std::true_type) \
1252+
const noexcept -> decltype(pmf) { \
1253+
return pmf; \
1254+
}
1255+
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&)
1256+
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&&)
1257+
1258+
#ifdef __cpp_noexcept_function_type
1259+
template <typename Return>
1260+
constexpr auto operator()(Return (*pf)(Args...) noexcept) const noexcept -> decltype(pf) {
1261+
return pf;
1262+
}
1263+
1264+
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(noexcept)
1265+
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(& noexcept)
1266+
PYBIND11_OVERLOAD_CAST_MEMBER_PTR(&& noexcept)
1267+
#endif
1268+
#undef PYBIND11_OVERLOAD_CAST_MEMBER_PTR
1269+
// NOLINTEND(bugprone-macro-parentheses)
12241270
};
12251271
PYBIND11_NAMESPACE_END(detail)
12261272

include/pybind11/numpy.h

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2327,4 +2327,86 @@ Helper vectorize(Return (Class::*f)(Args...) const) {
23272327
return Helper(std::mem_fn(f));
23282328
}
23292329

2330+
// Intentionally no &&/const&& overloads: vectorized method calls operate on the bound Python
2331+
// instance and should not consume/move-from self.
2332+
// Vectorize a class method (non-const, lvalue ref-qualified):
2333+
template <typename Return,
2334+
typename Class,
2335+
typename... Args,
2336+
typename Helper = detail::vectorize_helper<
2337+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) &>())),
2338+
Return,
2339+
Class *,
2340+
Args...>>
2341+
Helper vectorize(Return (Class::*f)(Args...) &) {
2342+
return Helper(std::mem_fn(f));
2343+
}
2344+
2345+
// Vectorize a class method (const, lvalue ref-qualified):
2346+
template <typename Return,
2347+
typename Class,
2348+
typename... Args,
2349+
typename Helper = detail::vectorize_helper<
2350+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const &>())),
2351+
Return,
2352+
const Class *,
2353+
Args...>>
2354+
Helper vectorize(Return (Class::*f)(Args...) const &) {
2355+
return Helper(std::mem_fn(f));
2356+
}
2357+
2358+
#ifdef __cpp_noexcept_function_type
2359+
// Vectorize a class method (non-const, noexcept):
2360+
template <typename Return,
2361+
typename Class,
2362+
typename... Args,
2363+
typename Helper = detail::vectorize_helper<
2364+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) noexcept>())),
2365+
Return,
2366+
Class *,
2367+
Args...>>
2368+
Helper vectorize(Return (Class::*f)(Args...) noexcept) {
2369+
return Helper(std::mem_fn(f));
2370+
}
2371+
2372+
// Vectorize a class method (const, noexcept):
2373+
template <typename Return,
2374+
typename Class,
2375+
typename... Args,
2376+
typename Helper = detail::vectorize_helper<
2377+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const noexcept>())),
2378+
Return,
2379+
const Class *,
2380+
Args...>>
2381+
Helper vectorize(Return (Class::*f)(Args...) const noexcept) {
2382+
return Helper(std::mem_fn(f));
2383+
}
2384+
2385+
// Vectorize a class method (non-const, lvalue ref-qualified, noexcept):
2386+
template <typename Return,
2387+
typename Class,
2388+
typename... Args,
2389+
typename Helper = detail::vectorize_helper<
2390+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) & noexcept>())),
2391+
Return,
2392+
Class *,
2393+
Args...>>
2394+
Helper vectorize(Return (Class::*f)(Args...) & noexcept) {
2395+
return Helper(std::mem_fn(f));
2396+
}
2397+
2398+
// Vectorize a class method (const, lvalue ref-qualified, noexcept):
2399+
template <typename Return,
2400+
typename Class,
2401+
typename... Args,
2402+
typename Helper = detail::vectorize_helper<
2403+
decltype(std::mem_fn(std::declval<Return (Class::*)(Args...) const & noexcept>())),
2404+
Return,
2405+
const Class *,
2406+
Args...>>
2407+
Helper vectorize(Return (Class::*f)(Args...) const & noexcept) {
2408+
return Helper(std::mem_fn(f));
2409+
}
2410+
#endif
2411+
23302412
PYBIND11_NAMESPACE_END(PYBIND11_NAMESPACE)

0 commit comments

Comments
 (0)