Skip to content

Commit 73cddbc

Browse files
committed
Simplify implementation
This simplifies the PR implementation by adding a `method_adaptor` function which returns a cpp_function wrapped around a lambda with the argument type switched to the requested type if given a member function pointer; otherwise the adaptor just forwards the argument to a cpp_function constructor.
1 parent b0fda6d commit 73cddbc

File tree

4 files changed

+63
-44
lines changed

4 files changed

+63
-44
lines changed

include/pybind11/attr.h

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -18,15 +18,7 @@ NAMESPACE_BEGIN(pybind11)
1818
/// @{
1919

2020
/// Annotation for methods
21-
template <typename CppClass>
22-
struct is_method {
23-
handle class_;
24-
is_method(const handle &c) : class_(c) { }
25-
using Class = CppClass;
26-
template <typename DeducedClass>
27-
using BindClass = detail::conditional_t<std::is_base_of<DeducedClass, Class>::value,
28-
Class, DeducedClass>;
29-
};
21+
struct is_method { handle class_; is_method(const handle &c) : class_(c) { } };
3022

3123
/// Annotation for operators
3224
struct is_operator { };
@@ -329,8 +321,8 @@ template <> struct process_attribute<sibling> : process_attribute_default<siblin
329321
};
330322

331323
/// Process an attribute which indicates that this function is a method
332-
template <typename Class> struct process_attribute<is_method<Class>> : process_attribute_default<is_method<Class>> {
333-
static void init(const is_method<Class> &s, function_record *r) { r->is_method = true; r->scope = s.class_; }
324+
template <> struct process_attribute<is_method> : process_attribute_default<is_method> {
325+
static void init(const is_method &s, function_record *r) { r->is_method = true; r->scope = s.class_; }
334326
};
335327

336328
/// Process an attribute which indicates the parent scope of a method
@@ -470,7 +462,7 @@ using extract_guard_t = typename exactly_one_t<is_call_guard, call_guard<>, Extr
470462
/// Check the number of named arguments at compile time
471463
template <typename... Extra,
472464
size_t named = constexpr_sum(std::is_base_of<arg, Extra>::value...),
473-
size_t self = constexpr_sum(is_instantiation<is_method, Extra>::value...)>
465+
size_t self = constexpr_sum(std::is_same<is_method, Extra>::value...)>
474466
constexpr bool expected_num_args(size_t nargs, bool has_args, bool has_kwargs) {
475467
return named == 0 || (self + named + has_args + has_kwargs) == nargs;
476468
}

include/pybind11/common.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -582,6 +582,11 @@ using exactly_one_t = typename exactly_one<Predicate, Default, Ts...>::type;
582582
template <typename T, typename... /*Us*/> struct deferred_type { using type = T; };
583583
template <typename T, typename... Us> using deferred_t = typename deferred_type<T, Us...>::type;
584584

585+
/// Like is_base_of, but requires a strict base (i.e. `is_strict_base_of<T, T>::value == false`,
586+
/// unlike `std::is_base_of`)
587+
template <typename Base, typename Derived> using is_strict_base_of = bool_constant<
588+
std::is_base_of<Base, Derived>::value && !std::is_base_of<Derived, Base>::value>;
589+
585590
template <template<typename...> class Base>
586591
struct is_template_base_of_impl {
587592
template <typename... Us> static std::true_type check(Base<Us...> *);

include/pybind11/pybind11.h

Lines changed: 48 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,6 @@ NAMESPACE_BEGIN(pybind11)
4444

4545
/// Wraps an arbitrary C++ function/method/lambda function/.. into a callable Python object
4646
class cpp_function : public function {
47-
private:
48-
template <typename Extra> using is_method_annotation = detail::is_instantiation<is_method, Extra>;
4947
public:
5048
cpp_function() { }
5149

@@ -71,19 +69,15 @@ class cpp_function : public function {
7169
/// Construct a cpp_function from a class method (non-const)
7270
template <typename Return, typename Class, typename... Arg, typename... Extra>
7371
cpp_function(Return (Class::*f)(Arg...), const Extra&... extra) {
74-
using ClassArg = typename detail::exactly_one_t<is_method_annotation, is_method<Class>, Extra...>
75-
::template BindClass<Class>;
76-
initialize([f](ClassArg *c, Arg... args) -> Return { return (c->*f)(args...); },
77-
(Return (*) (ClassArg *, Arg...)) nullptr, extra...);
72+
initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); },
73+
(Return (*) (Class *, Arg...)) nullptr, extra...);
7874
}
7975

8076
/// Construct a cpp_function from a class method (const)
8177
template <typename Return, typename Class, typename... Arg, typename... Extra>
8278
cpp_function(Return (Class::*f)(Arg...) const, const Extra&... extra) {
83-
using ClassArg = typename detail::exactly_one_t<is_method_annotation, is_method<Class>, Extra...>
84-
::template BindClass<Class>;
85-
initialize([f](const ClassArg *c, Arg... args) -> Return { return (c->*f)(args...); },
86-
(Return (*)(const ClassArg *, Arg ...)) nullptr, extra...);
79+
initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); },
80+
(Return (*)(const Class *, Arg ...)) nullptr, extra...);
8781
}
8882

8983
/// Return the function name
@@ -912,13 +906,40 @@ void call_operator_delete(T *p) { T::operator delete(p); }
912906

913907
inline void call_operator_delete(void *p) { ::operator delete(p); }
914908

909+
// Wrapper around cpp_function that binds a method pointer of a base class with a derived class
910+
// argument, as opposed to calling cpp_function directly which always uses the inferred class. For
911+
// any other type of argument, this is identical to cpp_function.
912+
template <typename Derived, typename Func, typename SFINAE = void> struct method_adaptor_impl {
913+
template <typename... Args> static cpp_function adapt(Args &&... args) { return {std::forward<Args>(args)...}; }
914+
};
915+
template <typename Derived, typename Return, typename Class, typename... Arg>
916+
struct method_adaptor_impl<Derived, Return (Class::*)(Arg...), enable_if_t<is_strict_base_of<Class, Derived>::value>> {
917+
template <typename... Extra> static cpp_function adapt(Return (Class::*f)(Arg...), const Extra&... extra) {
918+
return {[f](Derived *c, Arg... args) -> Return { return (c->*f)(args...); }, extra...};
919+
}
920+
};
921+
template <typename Derived, typename Return, typename Class, typename... Arg>
922+
struct method_adaptor_impl<Derived, Return (Class::*)(Arg...) const, enable_if_t<is_strict_base_of<Class, Derived>::value>> {
923+
template <typename... Extra> static cpp_function adapt(Return (Class::*f)(Arg...) const, const Extra&... extra) {
924+
return {[f](const Derived *c, Arg... args) -> Return { return (c->*f)(args...); }, extra...};
925+
}
926+
};
927+
915928
NAMESPACE_END(detail)
916929

930+
/// Returns a cpp_function around the given method or function; if given a method pointer to a base
931+
/// class of Derived, this results in a lambda that takes a `Derived *` as first argument, rather
932+
/// than the usual deduced `Base *` type.
933+
template <typename Derived, typename Func, typename... Extra> cpp_function method_adaptor(Func &&f, const Extra&... extra) {
934+
return detail::method_adaptor_impl<Derived, detail::remove_cv_t<detail::remove_reference_t<Func>>>::adapt(
935+
std::forward<Func>(f), extra...);
936+
}
937+
917938
template <typename type_, typename... options>
918939
class class_ : public detail::generic_type {
919940
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
920-
template <typename T> using is_subtype = detail::bool_constant<std::is_base_of<type_, T>::value && !std::is_same<T, type_>::value>;
921-
template <typename T> using is_base = detail::bool_constant<std::is_base_of<T, type_>::value && !std::is_same<T, type_>::value>;
941+
template <typename T> using is_subtype = detail::is_strict_base_of<type_, T>;
942+
template <typename T> using is_base = detail::is_strict_base_of<T, type_>;
922943
// struct instead of using here to help MSVC:
923944
template <typename T> struct is_valid_class_option :
924945
detail::any_of<is_holder<T>, is_subtype<T>, is_base<T>> {};
@@ -984,8 +1005,9 @@ class class_ : public detail::generic_type {
9841005

9851006
template <typename Func, typename... Extra>
9861007
class_ &def(const char *name_, Func&& f, const Extra&... extra) {
987-
cpp_function cf(std::forward<Func>(f), name(name_), is_method<type>(*this),
988-
sibling(getattr(*this, name_, none())), extra...);
1008+
auto cf = method_adaptor<type>(
1009+
std::forward<Func>(f), name(name_), is_method(*this),
1010+
sibling(getattr(*this, name_, none())), extra...);
9891011
attr(cf.name()) = cf;
9901012
return *this;
9911013
}
@@ -1048,18 +1070,18 @@ class class_ : public detail::generic_type {
10481070

10491071
template <typename C, typename D, typename... Extra>
10501072
class_ &def_readwrite(const char *name, D C::*pm, const Extra&... extra) {
1051-
using BindC = typename is_method<type>::template BindClass<C>;
1052-
def_property(name,
1053-
[pm](const BindC &c) -> const D &{ return c.*pm; },
1054-
[pm](BindC &c, const D &value) { c.*pm = value; },
1055-
extra...);
1073+
static_assert(std::is_base_of<C, type>::value, "def_readwrite() requires a class member (or base class member)");
1074+
cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)),
1075+
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
1076+
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
10561077
return *this;
10571078
}
10581079

10591080
template <typename C, typename D, typename... Extra>
10601081
class_ &def_readonly(const char *name, const D C::*pm, const Extra& ...extra) {
1061-
using BindC = typename is_method<type>::template BindClass<C>;
1062-
def_property_readonly(name, [pm](const BindC &c) -> const D &{ return c.*pm; }, extra...);
1082+
static_assert(std::is_base_of<C, type>::value, "def_readonly() requires a class member (or base class member)");
1083+
cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this));
1084+
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
10631085
return *this;
10641086
}
10651087

@@ -1081,7 +1103,8 @@ class class_ : public detail::generic_type {
10811103
/// Uses return_value_policy::reference_internal by default
10821104
template <typename Getter, typename... Extra>
10831105
class_ &def_property_readonly(const char *name, const Getter &fget, const Extra& ...extra) {
1084-
return def_property_readonly(name, cpp_function(fget, is_method<type>(*this)), return_value_policy::reference_internal, extra...);
1106+
return def_property_readonly(name, method_adaptor<type>(fget),
1107+
return_value_policy::reference_internal, extra...);
10851108
}
10861109

10871110
/// Uses cpp_function's return_value_policy by default
@@ -1105,21 +1128,17 @@ class class_ : public detail::generic_type {
11051128
/// Uses return_value_policy::reference_internal by default
11061129
template <typename Getter, typename Setter, typename... Extra>
11071130
class_ &def_property(const char *name, const Getter &fget, const Setter &fset, const Extra& ...extra) {
1108-
return def_property(name, fget,
1109-
cpp_function(fset, is_method<type>(*this), return_value_policy::reference_internal),
1110-
extra...);
1131+
return def_property(name, fget, method_adaptor<type>(fset, return_value_policy::reference_internal), extra...);
11111132
}
11121133
template <typename Getter, typename... Extra>
11131134
class_ &def_property(const char *name, const Getter &fget, const cpp_function &fset, const Extra& ...extra) {
1114-
return def_property(name,
1115-
cpp_function(fget, is_method<type>(*this), return_value_policy::reference_internal),
1116-
fset, extra...);
1135+
return def_property(name, method_adaptor<type>(fget), fset, return_value_policy::reference_internal, extra...);
11171136
}
11181137

11191138
/// Uses cpp_function's return_value_policy by default
11201139
template <typename... Extra>
11211140
class_ &def_property(const char *name, const cpp_function &fget, const cpp_function &fset, const Extra& ...extra) {
1122-
return def_property_static(name, fget, fset, is_method<type>(*this), extra...);
1141+
return def_property_static(name, fget, fset, is_method(*this), extra...);
11231142
}
11241143

11251144
/// Uses return_value_policy::reference by default

tests/test_methods_and_attributes.cpp

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ int none3(std::shared_ptr<NoneTester> &obj) { return obj ? obj->answer : -1; }
170170
int none4(std::shared_ptr<NoneTester> *obj) { return obj && *obj ? (*obj)->answer : -1; }
171171
int none5(std::shared_ptr<NoneTester> obj) { return obj ? obj->answer : -1; }
172172

173-
// Issue #854: incompatible function args when member function/pointer is in unregistered base class
173+
// Issues #854, #910: incompatible function args when member function/pointer is in unregistered base class
174174
class UnregisteredBase {
175175
public:
176176
void do_nothing() const {}
@@ -361,8 +361,8 @@ test_initializer methods_and_attributes([](py::module &m) {
361361
m.def("ok_none4", &none4, py::arg().none(true));
362362
m.def("ok_none5", &none5);
363363

364-
// Issue #854: incompatible function args when member function/pointer is in unregistered base class
365-
// The methods and member pointers below actually resolve to members/pointers in
364+
// Issues #854/910: incompatible function args when member function/pointer is in unregistered
365+
// base class The methods and member pointers below actually resolve to members/pointers in
366366
// UnregisteredBase; before this test/fix they would be registered via lambda with a first
367367
// argument of an unregistered type, and thus uncallable.
368368
py::class_<RegisteredDerived>(m, "RegisteredDerived")
@@ -371,6 +371,9 @@ test_initializer methods_and_attributes([](py::module &m) {
371371
.def("increase_value", &RegisteredDerived::increase_value)
372372
.def_readwrite("rw_value", &RegisteredDerived::rw_value)
373373
.def_readonly("ro_value", &RegisteredDerived::ro_value)
374+
// These should trigger a static_assert if uncommented
375+
//.def_readwrite("fails", &SimpleValue::value) // should trigger a static_assert if uncommented
376+
//.def_readonly("fails", &SimpleValue::value) // should trigger a static_assert if uncommented
374377
.def_property("rw_value_prop", &RegisteredDerived::get_int, &RegisteredDerived::set_int)
375378
.def_property_readonly("ro_value_prop", &RegisteredDerived::get_double)
376379
// This one is in the registered class:

0 commit comments

Comments
 (0)