Skip to content

Commit 7363268

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 04214b8 commit 7363268

File tree

4 files changed

+63
-45
lines changed

4 files changed

+63
-45
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 & 30 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

@@ -68,22 +66,17 @@ class cpp_function : public function {
6866
(FuncType *) nullptr, extra...);
6967
}
7068

71-
/// Construct a cpp_function from a class method (non-const)
7269
template <typename Return, typename Class, typename... Arg, typename... Extra>
7370
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...);
71+
initialize([f](Class *c, Arg... args) -> Return { return (c->*f)(args...); },
72+
(Return (*) (Class *, Arg...)) nullptr, extra...);
7873
}
7974

8075
/// Construct a cpp_function from a class method (const)
8176
template <typename Return, typename Class, typename... Arg, typename... Extra>
8277
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...);
78+
initialize([f](const Class *c, Arg... args) -> Return { return (c->*f)(args...); },
79+
(Return (*)(const Class *, Arg ...)) nullptr, extra...);
8780
}
8881

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

913906
inline void call_operator_delete(void *p) { ::operator delete(p); }
914907

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

929+
/// Returns a cpp_function around the given method or function; if given a method pointer to a base
930+
/// class of Derived, this results in a lambda that takes a `Derived *` as first argument, rather
931+
/// than the usual deduced `Base *` type.
932+
template <typename Derived, typename Func, typename... Extra> cpp_function method_adaptor(Func &&f, const Extra&... extra) {
933+
return detail::method_adaptor_impl<Derived, detail::remove_cv_t<detail::remove_reference_t<Func>>>::adapt(
934+
std::forward<Func>(f), extra...);
935+
}
936+
917937
template <typename type_, typename... options>
918938
class class_ : public detail::generic_type {
919939
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>;
940+
template <typename T> using is_subtype = detail::is_strict_base_of<type_, T>;
941+
template <typename T> using is_base = detail::is_strict_base_of<T, type_>;
922942
// struct instead of using here to help MSVC:
923943
template <typename T> struct is_valid_class_option :
924944
detail::any_of<is_holder<T>, is_subtype<T>, is_base<T>> {};
@@ -984,8 +1004,9 @@ class class_ : public detail::generic_type {
9841004

9851005
template <typename Func, typename... Extra>
9861006
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...);
1007+
auto cf = method_adaptor<type>(
1008+
std::forward<Func>(f), name(name_), is_method(*this),
1009+
sibling(getattr(*this, name_, none())), extra...);
9891010
attr(cf.name()) = cf;
9901011
return *this;
9911012
}
@@ -1048,18 +1069,18 @@ class class_ : public detail::generic_type {
10481069

10491070
template <typename C, typename D, typename... Extra>
10501071
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...);
1072+
static_assert(std::is_base_of<C, type>::value, "def_readwrite() requires a class member (or base class member)");
1073+
cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this)),
1074+
fset([pm](type &c, const D &value) { c.*pm = value; }, is_method(*this));
1075+
def_property(name, fget, fset, return_value_policy::reference_internal, extra...);
10561076
return *this;
10571077
}
10581078

10591079
template <typename C, typename D, typename... Extra>
10601080
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...);
1081+
static_assert(std::is_base_of<C, type>::value, "def_readonly() requires a class member (or base class member)");
1082+
cpp_function fget([pm](const type &c) -> const D &{ return c.*pm; }, is_method(*this));
1083+
def_property_readonly(name, fget, return_value_policy::reference_internal, extra...);
10631084
return *this;
10641085
}
10651086

@@ -1081,7 +1102,8 @@ class class_ : public detail::generic_type {
10811102
/// Uses return_value_policy::reference_internal by default
10821103
template <typename Getter, typename... Extra>
10831104
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...);
1105+
return def_property_readonly(name, method_adaptor<type>(fget),
1106+
return_value_policy::reference_internal, extra...);
10851107
}
10861108

10871109
/// Uses cpp_function's return_value_policy by default
@@ -1105,21 +1127,17 @@ class class_ : public detail::generic_type {
11051127
/// Uses return_value_policy::reference_internal by default
11061128
template <typename Getter, typename Setter, typename... Extra>
11071129
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...);
1130+
return def_property(name, fget, method_adaptor<type>(fset, return_value_policy::reference_internal), extra...);
11111131
}
11121132
template <typename Getter, typename... Extra>
11131133
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...);
1134+
return def_property(name, method_adaptor<type>(fget), fset, return_value_policy::reference_internal, extra...);
11171135
}
11181136

11191137
/// Uses cpp_function's return_value_policy by default
11201138
template <typename... Extra>
11211139
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...);
1140+
return def_property_static(name, fget, fset, is_method(*this), extra...);
11231141
}
11241142

11251143
/// 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)