Simplify py::init() type deduction and error checking

This commit is contained in:
Dean Moldovan 2017-08-24 00:42:51 +02:00
parent 39fd6a9463
commit 15f36d2b2d
2 changed files with 43 additions and 69 deletions

View File

@ -199,39 +199,28 @@ template <typename... Args> struct alias_constructor {
}; };
// Implementation class for py::init(Func) and py::init(Func, AliasFunc) // Implementation class for py::init(Func) and py::init(Func, AliasFunc)
template <typename CFunc, typename AFuncIn, typename... Args> template <typename CFunc, typename AFunc = void_type (*)(),
struct factory { typename = function_signature_t<CFunc>, typename = function_signature_t<AFunc>>
private: struct factory;
using CFuncType = typename std::remove_reference<CFunc>::type;
using AFunc = conditional_t<std::is_void<AFuncIn>::value, void_type, AFuncIn>;
using AFuncType = typename std::remove_reference<AFunc>::type;
CFuncType class_factory; // Specialization for py::init(Func)
AFuncType alias_factory; template <typename Func, typename Return, typename... Args>
struct factory<Func, void_type (*)(), Return(Args...)> {
remove_reference_t<Func> class_factory;
public: factory(Func &&f) : class_factory(std::forward<Func>(f)) { }
// Constructor with a single function/lambda to call; for classes without aliases or with
// aliases that can be move constructed from the base.
factory(CFunc &&f) : class_factory(std::forward<CFunc>(f)) {}
// Constructor with two functions/lambdas, for a class with distinct class/alias factories: the // The given class either has no alias or has no separate alias factory;
// first is called when an alias is not needed, the second when the alias is needed. Requires // this always constructs the class itself. If the class is registered with an alias
// non-void AFunc.
factory(CFunc &&c, AFunc &&a) :
class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
// Add __init__ definition for a class that either has no alias or has no separate alias
// factory; this always constructs the class itself. If the class is registered with an alias
// type and an alias instance is needed (i.e. because the final type is a Python class // type and an alias instance is needed (i.e. because the final type is a Python class
// inheriting from the C++ type) the returned value needs to either already be an alias // inheriting from the C++ type) the returned value needs to either already be an alias
// instance, or the alias needs to be constructible from a `Class &&` argument. // instance, or the alias needs to be constructible from a `Class &&` argument.
template <typename Class, typename... Extra, template <typename Class, typename... Extra>
enable_if_t<!Class::has_alias || std::is_void<AFuncIn>::value, int> = 0> void execute(Class &cl, const Extra &...extra) && {
void execute(Class &cl, const Extra&... extra) && {
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
cl.def("__init__", [func = std::move(class_factory)] cl.def("__init__", [func = std::move(class_factory)]
#else #else
CFuncType &func = class_factory; auto &func = class_factory;
cl.def("__init__", [func] cl.def("__init__", [func]
#endif #endif
(value_and_holder &v_h, Args... args) { (value_and_holder &v_h, Args... args) {
@ -239,64 +228,49 @@ public:
Py_TYPE(v_h.inst) != v_h.type->type); Py_TYPE(v_h.inst) != v_h.type->type);
}, is_new_style_constructor(), extra...); }, is_new_style_constructor(), extra...);
} }
};
// Add __init__ definition for a class with an alias *and* distinct alias factory; the former is // Specialization for py::init(Func, AliasFunc)
// called when the `self` type passed to `__init__` is the direct class (i.e. not inherited), the latter template <typename CFunc, typename AFunc,
// when `self` is a Python-side subtype. typename CReturn, typename... CArgs, typename AReturn, typename... AArgs>
template <typename Class, typename... Extra, struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
enable_if_t<Class::has_alias && !std::is_void<AFuncIn>::value, int> = 0> static_assert(sizeof...(CArgs) == sizeof...(AArgs),
"pybind11::init(class_factory, alias_factory): class and alias factories "
"must have identical argument signatures");
static_assert(all_of<std::is_same<CArgs, AArgs>...>::value,
"pybind11::init(class_factory, alias_factory): class and alias factories "
"must have identical argument signatures");
remove_reference_t<CFunc> class_factory;
remove_reference_t<AFunc> alias_factory;
factory(CFunc &&c, AFunc &&a)
: class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) { }
// The class factory is called when the `self` type passed to `__init__` is the direct
// class (i.e. not inherited), the alias factory when `self` is a Python-side subtype.
template <typename Class, typename... Extra>
void execute(Class &cl, const Extra&... extra) && { void execute(Class &cl, const Extra&... extra) && {
static_assert(Class::has_alias, "The two-argument version of `py::init()` can "
"only be used if the class has an alias");
#if defined(PYBIND11_CPP14) #if defined(PYBIND11_CPP14)
cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)] cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
#else #else
CFuncType &class_func = class_factory; auto &class_func = class_factory;
AFuncType &alias_func = alias_factory; auto &alias_func = alias_factory;
cl.def("__init__", [class_func, alias_func] cl.def("__init__", [class_func, alias_func]
#endif #endif
(value_and_holder &v_h, Args... args) { (value_and_holder &v_h, CArgs... args) {
if (Py_TYPE(v_h.inst) == v_h.type->type) if (Py_TYPE(v_h.inst) == v_h.type->type)
// If the instance type equals the registered type we don't have inheritance, so // If the instance type equals the registered type we don't have inheritance, so
// don't need the alias and can construct using the class function: // don't need the alias and can construct using the class function:
construct<Class>(v_h, class_func(std::forward<Args>(args)...), false); construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false);
else else
construct<Class>(v_h, alias_func(std::forward<Args>(args)...), true); construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true);
}, is_new_style_constructor(), extra...); }, is_new_style_constructor(), extra...);
} }
}; };
template <typename Func> using functype =
conditional_t<std::is_function<remove_reference_t<Func>>::value, remove_reference_t<Func> *,
conditional_t<is_function_pointer<remove_reference_t<Func>>::value, remove_reference_t<Func>,
Func>>;
// Helper definition to infer the detail::initimpl::factory template types from a callable object
template <typename Func, typename Return, typename... Args>
factory<functype<Func>, void, Args...> func_decltype(Return (*)(Args...));
// metatemplate that ensures the Class and Alias factories take identical arguments: we need to be
// able to call either one with the given arguments (depending on the final instance type).
template <typename Return1, typename Return2, typename... Args1, typename... Args2>
inline constexpr bool require_matching_arguments(Return1 (*)(Args1...), Return2 (*)(Args2...)) {
static_assert(sizeof...(Args1) == sizeof...(Args2),
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
static_assert(all_of<std::is_same<Args1, Args2>...>::value,
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
return true;
}
// Unimplemented function provided only for its type signature (via `decltype`), which resolves to
// the appropriate specialization of the above `init` struct with the appropriate function, argument
// and return types.
template <typename CFunc, typename AFunc,
typename CReturn, typename... CArgs, typename AReturn, typename... AArgs,
bool = require_matching_arguments((CReturn (*)(CArgs...)) nullptr, (AReturn (*)(AArgs...)) nullptr)>
factory<functype<CFunc>, functype<AFunc>, CArgs...> func_decltype(CReturn (*)(CArgs...), AReturn (*)(AArgs...));
// Resolves to the appropriate specialization of the `pybind11::detail::initimpl::factory<...>` for a
// given init function or pair of class/alias init functions.
template <typename... Func> using factory_t = decltype(func_decltype<Func...>(
(function_signature_t<Func> *) nullptr...));
NAMESPACE_END(initimpl) NAMESPACE_END(initimpl)
NAMESPACE_END(detail) NAMESPACE_END(detail)
NAMESPACE_END(pybind11) NAMESPACE_END(pybind11)

View File

@ -1389,12 +1389,12 @@ template <typename... Args> detail::initimpl::constructor<Args...> init() { retu
template <typename... Args> detail::initimpl::alias_constructor<Args...> init_alias() { return {}; } template <typename... Args> detail::initimpl::alias_constructor<Args...> init_alias() { return {}; }
/// Binds a factory function as a constructor /// Binds a factory function as a constructor
template <typename Func, typename Ret = detail::initimpl::factory_t<Func>> template <typename Func, typename Ret = detail::initimpl::factory<Func>>
Ret init(Func &&f) { return {std::forward<Func>(f)}; } Ret init(Func &&f) { return {std::forward<Func>(f)}; }
/// Dual-argument factory function: the first function is called when no alias is needed, the second /// Dual-argument factory function: the first function is called when no alias is needed, the second
/// when an alias is needed (i.e. due to python-side inheritance). Arguments must be identical. /// when an alias is needed (i.e. due to python-side inheritance). Arguments must be identical.
template <typename CFunc, typename AFunc, typename Ret = detail::initimpl::factory_t<CFunc, AFunc>> template <typename CFunc, typename AFunc, typename Ret = detail::initimpl::factory<CFunc, AFunc>>
Ret init(CFunc &&c, AFunc &&a) { Ret init(CFunc &&c, AFunc &&a) {
return {std::forward<CFunc>(c), std::forward<AFunc>(a)}; return {std::forward<CFunc>(c), std::forward<AFunc>(a)};
} }