Skip to content

Commit ba63458

Browse files
committed
Simplify method adapter with macro and add missing rvalue adaptors + tests
1 parent 70170b0 commit ba63458

3 files changed

Lines changed: 190 additions & 68 deletions

File tree

include/pybind11/pybind11.h

Lines changed: 113 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -370,6 +370,30 @@ class cpp_function : public function {
370370
extra...);
371371
}
372372

373+
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier)
374+
template <typename Return, typename Class, typename... Arg, typename... Extra>
375+
// NOLINTNEXTLINE(google-explicit-constructor)
376+
cpp_function(Return (Class::*f)(Arg...) &&, const Extra &...extra) {
377+
initialize(
378+
[f](Class *c, Arg... args) -> Return {
379+
return (std::move(*c).*f)(std::forward<Arg>(args)...);
380+
},
381+
(Return (*)(Class *, Arg...)) nullptr,
382+
extra...);
383+
}
384+
385+
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier)
386+
template <typename Return, typename Class, typename... Arg, typename... Extra>
387+
// NOLINTNEXTLINE(google-explicit-constructor)
388+
cpp_function(Return (Class::*f)(Arg...) const &&, const Extra &...extra) {
389+
initialize(
390+
[f](const Class *c, Arg... args) -> Return {
391+
return (std::move(*c).*f)(std::forward<Arg>(args)...);
392+
},
393+
(Return (*)(const Class *, Arg...)) nullptr,
394+
extra...);
395+
}
396+
373397
#ifdef __cpp_noexcept_function_type
374398
/// Construct a cpp_function from a class method (non-const, no ref-qualifier, noexcept)
375399
template <typename Return, typename Class, typename... Arg, typename... Extra>
@@ -410,6 +434,30 @@ class cpp_function : public function {
410434
(Return (*)(const Class *, Arg...)) nullptr,
411435
extra...);
412436
}
437+
438+
/// Construct a cpp_function from a class method (non-const, rvalue ref-qualifier, noexcept)
439+
template <typename Return, typename Class, typename... Arg, typename... Extra>
440+
// NOLINTNEXTLINE(google-explicit-constructor)
441+
cpp_function(Return (Class::*f)(Arg...) && noexcept, const Extra &...extra) {
442+
initialize(
443+
[f](Class *c, Arg... args) -> Return {
444+
return (std::move(*c).*f)(std::forward<Arg>(args)...);
445+
},
446+
(Return (*)(Class *, Arg...)) nullptr,
447+
extra...);
448+
}
449+
450+
/// Construct a cpp_function from a class method (const, rvalue ref-qualifier, noexcept)
451+
template <typename Return, typename Class, typename... Arg, typename... Extra>
452+
// NOLINTNEXTLINE(google-explicit-constructor)
453+
cpp_function(Return (Class::*f)(Arg...) const && noexcept, const Extra &...extra) {
454+
initialize(
455+
[f](const Class *c, Arg... args) -> Return {
456+
return (std::move(*c).*f)(std::forward<Arg>(args)...);
457+
},
458+
(Return (*)(const Class *, Arg...)) nullptr,
459+
extra...);
460+
}
413461
#endif
414462

415463
/// Return the function name
@@ -1922,6 +1970,49 @@ inline void add_class_method(object &cls, const char *name_, const cpp_function
19221970
}
19231971
}
19241972

1973+
/// Type trait to rebind a member function pointer's class to `Derived`, preserving all
1974+
/// cv/ref/noexcept qualifiers. The primary template has no `type` member, providing SFINAE
1975+
/// failure for unsupported member function pointer types. `source_class` holds the original
1976+
/// class for use in `is_accessible_base_of` checks.
1977+
template <typename Derived, typename T>
1978+
struct rebind_member_ptr {};
1979+
1980+
// Define one specialization per supported qualifier combination via a local macro.
1981+
#define PYBIND11_REBIND_MEMBER_PTR(qualifiers) \
1982+
template <typename Derived, typename Return, typename Class, typename... Args> \
1983+
struct rebind_member_ptr<Derived, Return (Class::*)(Args...) qualifiers> { \
1984+
using type = Return (Derived::*)(Args...) qualifiers; \
1985+
using source_class = Class; \
1986+
}
1987+
PYBIND11_REBIND_MEMBER_PTR();
1988+
PYBIND11_REBIND_MEMBER_PTR(const);
1989+
PYBIND11_REBIND_MEMBER_PTR(&);
1990+
PYBIND11_REBIND_MEMBER_PTR(const &);
1991+
PYBIND11_REBIND_MEMBER_PTR(&&);
1992+
PYBIND11_REBIND_MEMBER_PTR(const &&);
1993+
#ifdef __cpp_noexcept_function_type
1994+
PYBIND11_REBIND_MEMBER_PTR(noexcept);
1995+
PYBIND11_REBIND_MEMBER_PTR(const noexcept);
1996+
PYBIND11_REBIND_MEMBER_PTR(& noexcept);
1997+
PYBIND11_REBIND_MEMBER_PTR(const & noexcept);
1998+
PYBIND11_REBIND_MEMBER_PTR(&& noexcept);
1999+
PYBIND11_REBIND_MEMBER_PTR(const && noexcept);
2000+
#endif
2001+
#undef PYBIND11_REBIND_MEMBER_PTR
2002+
2003+
/// Shared implementation body for all method_adaptor member-function-pointer overloads.
2004+
/// Asserts Base is accessible from Derived, then casts the member pointer.
2005+
template <typename Derived,
2006+
typename T,
2007+
typename Traits = rebind_member_ptr<Derived, T>,
2008+
typename Adapted = typename Traits::type>
2009+
PYBIND11_ALWAYS_INLINE Adapted adapt_member_ptr(T pmf) {
2010+
static_assert(
2011+
is_accessible_base_of<typename Traits::source_class, Derived>::value,
2012+
"Cannot bind an inaccessible base class method; use a lambda definition instead");
2013+
return pmf;
2014+
}
2015+
19252016
PYBIND11_NAMESPACE_END(detail)
19262017

19272018
/// Given a pointer to a member function, cast it to its `Derived` version.
@@ -1931,76 +2022,30 @@ auto method_adaptor(F &&f) -> decltype(std::forward<F>(f)) {
19312022
return std::forward<F>(f);
19322023
}
19332024

1934-
template <typename Derived, typename Return, typename Class, typename... Args>
1935-
auto method_adaptor(Return (Class::*pmf)(Args...)) -> Return (Derived::*)(Args...) {
1936-
static_assert(
1937-
detail::is_accessible_base_of<Class, Derived>::value,
1938-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1939-
return pmf;
1940-
}
1941-
1942-
template <typename Derived, typename Return, typename Class, typename... Args>
1943-
auto method_adaptor(Return (Class::*pmf)(Args...) const) -> Return (Derived::*)(Args...) const {
1944-
static_assert(
1945-
detail::is_accessible_base_of<Class, Derived>::value,
1946-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1947-
return pmf;
1948-
}
1949-
1950-
template <typename Derived, typename Return, typename Class, typename... Args>
1951-
auto method_adaptor(Return (Class::*pmf)(Args...) &) -> Return (Derived::*)(Args...) & {
1952-
static_assert(
1953-
detail::is_accessible_base_of<Class, Derived>::value,
1954-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1955-
return pmf;
1956-
}
1957-
1958-
template <typename Derived, typename Return, typename Class, typename... Args>
1959-
auto method_adaptor(Return (Class::*pmf)(Args...) const &)
1960-
-> Return (Derived::*)(Args...) const & {
1961-
static_assert(
1962-
detail::is_accessible_base_of<Class, Derived>::value,
1963-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1964-
return pmf;
1965-
}
1966-
2025+
// One thin overload per supported member-function-pointer qualifier combination.
2026+
// Specific parameter types are required so partial ordering prefers these over the F&& fallback.
2027+
// The shared body (static_assert + implicit cast) lives in detail::adapt_member_ptr.
2028+
#define PYBIND11_METHOD_ADAPTOR(qualifiers) \
2029+
template <typename Derived, typename Return, typename Class, typename... Args> \
2030+
auto method_adaptor(Return (Class::*pmf)(Args...) qualifiers) -> Return (Derived::*)(Args...) \
2031+
qualifiers { \
2032+
return detail::adapt_member_ptr<Derived>(pmf); \
2033+
}
2034+
PYBIND11_METHOD_ADAPTOR()
2035+
PYBIND11_METHOD_ADAPTOR(const)
2036+
PYBIND11_METHOD_ADAPTOR(&)
2037+
PYBIND11_METHOD_ADAPTOR(const &)
2038+
PYBIND11_METHOD_ADAPTOR(&&)
2039+
PYBIND11_METHOD_ADAPTOR(const &&)
19672040
#ifdef __cpp_noexcept_function_type
1968-
template <typename Derived, typename Return, typename Class, typename... Args>
1969-
auto method_adaptor(Return (Class::*pmf)(Args...) noexcept)
1970-
-> Return (Derived::*)(Args...) noexcept {
1971-
static_assert(
1972-
detail::is_accessible_base_of<Class, Derived>::value,
1973-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1974-
return pmf;
1975-
}
1976-
1977-
template <typename Derived, typename Return, typename Class, typename... Args>
1978-
auto method_adaptor(Return (Class::*pmf)(Args...) const noexcept)
1979-
-> Return (Derived::*)(Args...) const noexcept {
1980-
static_assert(
1981-
detail::is_accessible_base_of<Class, Derived>::value,
1982-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1983-
return pmf;
1984-
}
1985-
1986-
template <typename Derived, typename Return, typename Class, typename... Args>
1987-
auto method_adaptor(Return (Class::*pmf)(Args...) & noexcept)
1988-
-> Return (Derived::*)(Args...) & noexcept {
1989-
static_assert(
1990-
detail::is_accessible_base_of<Class, Derived>::value,
1991-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
1992-
return pmf;
1993-
}
1994-
1995-
template <typename Derived, typename Return, typename Class, typename... Args>
1996-
auto method_adaptor(Return (Class::*pmf)(Args...) const & noexcept)
1997-
-> Return (Derived::*)(Args...) const & noexcept {
1998-
static_assert(
1999-
detail::is_accessible_base_of<Class, Derived>::value,
2000-
"Cannot bind an inaccessible base class method; use a lambda definition instead");
2001-
return pmf;
2002-
}
2041+
PYBIND11_METHOD_ADAPTOR(noexcept)
2042+
PYBIND11_METHOD_ADAPTOR(const noexcept)
2043+
PYBIND11_METHOD_ADAPTOR(& noexcept)
2044+
PYBIND11_METHOD_ADAPTOR(const & noexcept)
2045+
PYBIND11_METHOD_ADAPTOR(&& noexcept)
2046+
PYBIND11_METHOD_ADAPTOR(const && noexcept)
20032047
#endif
2048+
#undef PYBIND11_METHOD_ADAPTOR
20042049

20052050
PYBIND11_NAMESPACE_BEGIN(detail)
20062051

tests/test_methods_and_attributes.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -183,6 +183,29 @@ class NoexceptDerived : public NoexceptUnregisteredBase {
183183
using NoexceptUnregisteredBase::NoexceptUnregisteredBase;
184184
};
185185

186+
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...) and
187+
// cpp_function(Return (Class::*)(Args...) const &&, ...) via an unregistered base.
188+
class RValueRefUnregisteredBase {
189+
public:
190+
// Exercises cpp_function(Return (Class::*)(Args...) &&, ...)
191+
int take() && { return m_value; }
192+
// Exercises cpp_function(Return (Class::*)(Args...) const &&, ...)
193+
int peek() const && { return m_value; }
194+
#ifdef __cpp_noexcept_function_type
195+
// Exercises cpp_function(Return (Class::*)(Args...) && noexcept, ...)
196+
int take_noexcept() && noexcept { return m_value; }
197+
// Exercises cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
198+
int peek_noexcept() const && noexcept { return m_value; }
199+
#endif
200+
201+
private:
202+
int m_value = 77;
203+
};
204+
class RValueRefDerived : public RValueRefUnregisteredBase {
205+
public:
206+
using RValueRefUnregisteredBase::RValueRefUnregisteredBase;
207+
};
208+
186209
// Exercises overload_cast with noexcept member function pointers (issue #2234).
187210
// In C++17, overload_cast must have noexcept variants to resolve noexcept overloads.
188211
struct NoexceptOverloaded {
@@ -521,6 +544,29 @@ TEST_SUBMODULE(methods_and_attributes, m) {
521544
// cpp_function(Return (Class::*)(Args...) const & noexcept, ...)
522545
.def("capped_value", &NoexceptDerived::capped_value);
523546

547+
// test_rvalue_ref_qualified_methods: rvalue-ref-qualified methods from an unregistered base.
548+
// method_adaptor must rebind &&/const&& member pointers to the derived type.
549+
py::class_<RValueRefDerived>(m, "RValueRefDerived")
550+
.def(py::init<>())
551+
// cpp_function(Return (Class::*)(Args...) &&, ...)
552+
.def("take", &RValueRefDerived::take)
553+
// cpp_function(Return (Class::*)(Args...) const &&, ...)
554+
.def("peek", &RValueRefDerived::peek)
555+
#ifdef __cpp_noexcept_function_type
556+
// cpp_function(Return (Class::*)(Args...) && noexcept, ...)
557+
.def("take_noexcept", &RValueRefDerived::take_noexcept)
558+
// cpp_function(Return (Class::*)(Args...) const && noexcept, ...)
559+
.def("peek_noexcept", &RValueRefDerived::peek_noexcept)
560+
#endif
561+
;
562+
563+
// Verify method_adaptor preserves &&/const&& qualifiers when rebinding.
564+
using AdaptedRRef = decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take));
565+
static_assert(std::is_same<AdaptedRRef, int (RValueRefDerived::*)() &&>::value, "");
566+
using AdaptedConstRRef
567+
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek));
568+
static_assert(std::is_same<AdaptedConstRRef, int (RValueRefDerived::*)() const &&>::value, "");
569+
524570
#ifdef __cpp_noexcept_function_type
525571
// method_adaptor must also handle noexcept member function pointers (issue #2234).
526572
// Verify the noexcept specifier is preserved in the resulting Derived pointer type.
@@ -532,6 +578,15 @@ TEST_SUBMODULE(methods_and_attributes, m) {
532578
= decltype(py::method_adaptor<NoexceptDerived>(&NoexceptDerived::set_value));
533579
static_assert(std::is_same<AdaptedNoexcept, void (NoexceptDerived::*)(int) noexcept>::value,
534580
"");
581+
using AdaptedRRefNoexcept
582+
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::take_noexcept));
583+
static_assert(
584+
std::is_same < AdaptedRRefNoexcept, int (RValueRefDerived::*)() && noexcept > ::value, "");
585+
using AdaptedConstRRefNoexcept
586+
= decltype(py::method_adaptor<RValueRefDerived>(&RValueRefDerived::peek_noexcept));
587+
static_assert(std::is_same < AdaptedConstRRefNoexcept,
588+
int (RValueRefDerived::*)() const && noexcept > ::value,
589+
"");
535590
#endif
536591

537592
// test_noexcept_overload_cast (issue #2234)

tests/test_methods_and_attributes.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -545,6 +545,28 @@ def test_noexcept_base():
545545
assert obj.capped_value() == 100 # capped at 100
546546

547547

548+
def test_rvalue_ref_qualified_methods():
549+
"""Test that rvalue-ref-qualified (&&/const&&) methods from an unregistered base bind
550+
correctly with `self` resolved to the derived type.
551+
552+
Covers:
553+
- Return (Class::*)(Args...) && (take)
554+
- Return (Class::*)(Args...) const && (peek)
555+
- Return (Class::*)(Args...) && noexcept (take_noexcept, C++17 only)
556+
- Return (Class::*)(Args...) const && noexcept (peek_noexcept, C++17 only)
557+
"""
558+
obj = m.RValueRefDerived()
559+
# && (rvalue ref-qualified, non-const)
560+
assert obj.take() == 77
561+
# const && (rvalue ref-qualified, const)
562+
assert obj.peek() == 77
563+
# noexcept variants are bound only under C++17; skip gracefully if absent
564+
if hasattr(obj, "take_noexcept"):
565+
assert obj.take_noexcept() == 77
566+
if hasattr(obj, "peek_noexcept"):
567+
assert obj.peek_noexcept() == 77
568+
569+
548570
def test_noexcept_overload_cast():
549571
"""Test issue #2234: overload_cast must handle noexcept member and free function pointers.
550572

0 commit comments

Comments
 (0)