Allow arbitrary class_ template option ordering

The current pybind11::class_<Type, Holder, Trampoline> fixed template
ordering results in a requirement to repeat the Holder with its default
value (std::unique_ptr<Type>) argument, which is a little bit annoying:
it needs to be specified not because we want to override the default,
but rather because we need to specify the third argument.

This commit removes this limitation by making the class_ template take
the type name plus a parameter pack of options.  It then extracts the
first valid holder type and the first subclass type for holder_type and
trampoline type_alias, respectively.  (If unfound, both fall back to
their current defaults, `std::unique_ptr<type>` and `type`,
respectively).  If any unmatched template arguments are provided, a
static assertion fails.

What this means is that you can specify or omit the arguments in any
order:

    py::class_<A, PyA> c1(m, "A");
    py::class_<B, PyB, std::shared_ptr<B>> c2(m, "B");
    py::class_<C, std::shared_ptr<C>, PyB> c3(m, "C");

It also allows future class attributes (such as base types in the next
commit) to be passed as class template types rather than needing to use
a py::base<> wrapper.
This commit is contained in:
Jason Rhinelander
2016-09-06 12:17:06 -04:00
parent a3dbdc67f5
commit 5fffe200e3
10 changed files with 187 additions and 52 deletions

View File

@@ -798,6 +798,13 @@ protected:
holder_type holder;
};
template <typename base, typename holder> struct is_holder_type :
// PYBIND11_DECLARE_HOLDER_TYPE holder types:
std::conditional<std::is_base_of<detail::type_caster_holder<base, holder>, detail::type_caster<holder>>::value,
std::true_type,
std::false_type>::type {};
template <typename base, typename deleter> struct is_holder_type<base, std::unique_ptr<base, deleter>> : std::true_type {};
template <typename T> struct handle_type_name { static PYBIND11_DESCR name() { return _<T>(); } };
template <> struct handle_type_name<bytes> { static PYBIND11_DESCR name() { return _(PYBIND11_BYTES_NAME); } };
template <> struct handle_type_name<args> { static PYBIND11_DESCR name() { return _("*args"); } };

View File

@@ -358,6 +358,28 @@ template <template<typename> class P, typename T, typename... Ts>
struct any_of_t<P, T, Ts...> : conditional_t<P<T>::value, std::true_type, any_of_t<P, Ts...>> { };
#endif
// Extracts the first type from the template parameter pack matching the predicate, or void if none match.
template <template<class> class Predicate, class... Ts> struct first_of;
template <template<class> class Predicate> struct first_of<Predicate> {
using type = void;
};
template <template<class> class Predicate, class T, class... Ts>
struct first_of<Predicate, T, Ts...> {
using type = typename std::conditional<
Predicate<T>::value,
T,
typename first_of<Predicate, Ts...>::type
>::type;
};
template <template<class> class Predicate, class... T> using first_of_t = typename first_of<Predicate, T...>::type;
// Counts the number of types in the template parameter pack matching the predicate
template <template<typename> class Predicate, typename... Ts> struct count_t;
template <template<typename> class Predicate> struct count_t<Predicate> : std::integral_constant<size_t, 0> {};
template <template<typename> class Predicate, class T, class... Ts>
struct count_t<Predicate, T, Ts...> : std::integral_constant<size_t,
Predicate<T>::value + count_t<Predicate, Ts...>::value> {};
/// Defer the evaluation of type T until types Us are instantiated
template <typename T, typename... /*Us*/> struct deferred_type { using type = T; };
template <typename T, typename... Us> using deferred_t = typename deferred_type<T, Us...>::type;

View File

@@ -49,17 +49,19 @@ template <op_id, op_type, typename B, typename L, typename R> struct op_impl { }
/// Operator implementation generator
template <op_id id, op_type ot, typename L, typename R> struct op_ {
template <typename Base, typename Holder, typename... Extra> void execute(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
template <typename Class, typename... Extra> void execute(Class &cl, const Extra&... extra) const {
typedef typename Class::type Base;
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
typedef op_impl<id, ot, Base, L_type, R_type> op;
class_.def(op::name(), &op::execute, extra...);
cl.def(op::name(), &op::execute, extra...);
}
template <typename Base, typename Holder, typename... Extra> void execute_cast(pybind11::class_<Base, Holder> &class_, const Extra&... extra) const {
template <typename Class, typename... Extra> void execute_cast(Class &cl, const Extra&... extra) const {
typedef typename Class::type Base;
typedef typename std::conditional<std::is_same<L, self_t>::value, Base, L>::type L_type;
typedef typename std::conditional<std::is_same<R, self_t>::value, Base, R>::type R_type;
typedef op_impl<id, ot, Base, L_type, R_type> op;
class_.def(op::name(), &op::execute_cast, extra...);
cl.def(op::name(), &op::execute_cast, extra...);
}
};

View File

@@ -563,7 +563,7 @@ public:
NAMESPACE_BEGIN(detail)
/// Generic support for creating new Python heap types
class generic_type : public object {
template <typename type, typename holder_type, typename type_alias> friend class class_;
template <typename...> friend class class_;
public:
PYBIND11_OBJECT_DEFAULT(generic_type, object, PyType_Check)
protected:
@@ -804,10 +804,31 @@ protected:
};
NAMESPACE_END(detail)
template <typename type, typename holder_type = std::unique_ptr<type>, typename type_alias = type>
template <typename type_, typename... options>
class class_ : public detail::generic_type {
template <typename T> using is_holder = detail::is_holder_type<type_, T>;
template <typename T> using is_subtype = detail::bool_constant<std::is_base_of<type_, T>::value && !std::is_same<T, type_>::value>;
template <typename T> using is_valid_class_option =
detail::bool_constant<
is_holder<T>::value ||
is_subtype<T>::value
>;
using extracted_holder_t = typename detail::first_of_t<is_holder, options...>;
public:
typedef detail::instance<type, holder_type> instance_type;
using type = type_;
using type_alias = detail::first_of_t<is_subtype, options...>;
constexpr static bool has_alias = !std::is_void<type_alias>::value;
using holder_type = typename std::conditional<
std::is_void<extracted_holder_t>::value,
std::unique_ptr<type>,
extracted_holder_t
>::type;
using instance_type = detail::instance<type, holder_type>;
static_assert(detail::all_of_t<is_valid_class_option, options...>::value,
"Unknown/invalid class_ template parameters provided");
PYBIND11_OBJECT(class_, detail::generic_type, PyType_Check)
@@ -827,7 +848,7 @@ public:
detail::generic_type::initialize(&record);
if (!std::is_same<type, type_alias>::value) {
if (has_alias) {
auto &instances = pybind11::detail::get_internals().registered_types_cpp;
instances[std::type_index(typeid(type_alias))] = instances[std::type_index(typeid(type))];
}
@@ -852,25 +873,25 @@ public:
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
class_ &def(const detail::op_<id, ot, L, R> &op, const Extra&... extra) {
op.template execute<type>(*this, extra...);
op.execute(*this, extra...);
return *this;
}
template <detail::op_id id, detail::op_type ot, typename L, typename R, typename... Extra>
class_ & def_cast(const detail::op_<id, ot, L, R> &op, const Extra&... extra) {
op.template execute_cast<type>(*this, extra...);
op.execute_cast(*this, extra...);
return *this;
}
template <typename... Args, typename... Extra>
class_ &def(const detail::init<Args...> &init, const Extra&... extra) {
init.template execute<type>(*this, extra...);
init.execute(*this, extra...);
return *this;
}
template <typename... Args, typename... Extra>
class_ &def(const detail::init_alias<Args...> &init, const Extra&... extra) {
init.template execute<type>(*this, extra...);
init.execute(*this, extra...);
return *this;
}
@@ -1071,19 +1092,21 @@ private:
NAMESPACE_BEGIN(detail)
template <typename... Args> struct init {
template <typename Base, typename Holder, typename Alias, typename... Extra,
typename std::enable_if<std::is_same<Base, Alias>::value, int>::type = 0>
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
template <typename Class, typename... Extra, typename std::enable_if<!Class::has_alias, int>::type = 0>
void execute(Class &cl, const Extra&... extra) const {
using Base = typename Class::type;
/// Function which calls a specific C++ in-place constructor
class_.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
cl.def("__init__", [](Base *self_, Args... args) { new (self_) Base(args...); }, extra...);
}
template <typename Base, typename Holder, typename Alias, typename... Extra,
typename std::enable_if<!std::is_same<Base, Alias>::value &&
std::is_constructible<Base, Args...>::value, int>::type = 0>
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
handle cl_type = class_;
class_.def("__init__", [cl_type](handle self_, Args... args) {
template <typename Class, typename... Extra,
typename std::enable_if<Class::has_alias &&
std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
void execute(Class &cl, const Extra&... extra) const {
using Base = typename Class::type;
using Alias = typename Class::type_alias;
handle cl_type = cl;
cl.def("__init__", [cl_type](handle self_, Args... args) {
if (self_.get_type() == cl_type)
new (self_.cast<Base *>()) Base(args...);
else
@@ -1091,11 +1114,12 @@ template <typename... Args> struct init {
}, extra...);
}
template <typename Base, typename Holder, typename Alias, typename... Extra,
typename std::enable_if<!std::is_same<Base, Alias>::value &&
!std::is_constructible<Base, Args...>::value, int>::type = 0>
void execute(pybind11::class_<Base, Holder, Alias> &class_, const Extra&... extra) const {
class_.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
template <typename Class, typename... Extra,
typename std::enable_if<Class::has_alias &&
!std::is_constructible<typename Class::type, Args...>::value, int>::type = 0>
void execute(Class &cl, const Extra&... extra) const {
using Alias = typename Class::type_alias;
cl.def("__init__", [](Alias *self_, Args... args) { new (self_) Alias(args...); }, extra...);
}
};