Skip to content

Commit 15f36d2

Browse files
committed
Simplify py::init() type deduction and error checking
1 parent 39fd6a9 commit 15f36d2

File tree

2 files changed

+44
-70
lines changed

2 files changed

+44
-70
lines changed

include/pybind11/detail/init.h

Lines changed: 42 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -199,104 +199,78 @@ template <typename... Args> struct alias_constructor {
199199
};
200200

201201
// Implementation class for py::init(Func) and py::init(Func, AliasFunc)
202-
template <typename CFunc, typename AFuncIn, typename... Args>
203-
struct factory {
204-
private:
205-
using CFuncType = typename std::remove_reference<CFunc>::type;
206-
using AFunc = conditional_t<std::is_void<AFuncIn>::value, void_type, AFuncIn>;
207-
using AFuncType = typename std::remove_reference<AFunc>::type;
202+
template <typename CFunc, typename AFunc = void_type (*)(),
203+
typename = function_signature_t<CFunc>, typename = function_signature_t<AFunc>>
204+
struct factory;
205+
206+
// Specialization for py::init(Func)
207+
template <typename Func, typename Return, typename... Args>
208+
struct factory<Func, void_type (*)(), Return(Args...)> {
209+
remove_reference_t<Func> class_factory;
208210

209-
CFuncType class_factory;
210-
AFuncType alias_factory;
211+
factory(Func &&f) : class_factory(std::forward<Func>(f)) { }
211212

212-
public:
213-
// Constructor with a single function/lambda to call; for classes without aliases or with
214-
// aliases that can be move constructed from the base.
215-
factory(CFunc &&f) : class_factory(std::forward<CFunc>(f)) {}
216-
217-
// Constructor with two functions/lambdas, for a class with distinct class/alias factories: the
218-
// first is called when an alias is not needed, the second when the alias is needed. Requires
219-
// non-void AFunc.
220-
factory(CFunc &&c, AFunc &&a) :
221-
class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) {}
222-
223-
// Add __init__ definition for a class that either has no alias or has no separate alias
224-
// factory; this always constructs the class itself. If the class is registered with an alias
213+
// The given class either has no alias or has no separate alias factory;
214+
// this always constructs the class itself. If the class is registered with an alias
225215
// type and an alias instance is needed (i.e. because the final type is a Python class
226216
// inheriting from the C++ type) the returned value needs to either already be an alias
227217
// instance, or the alias needs to be constructible from a `Class &&` argument.
228-
template <typename Class, typename... Extra,
229-
enable_if_t<!Class::has_alias || std::is_void<AFuncIn>::value, int> = 0>
230-
void execute(Class &cl, const Extra&... extra) && {
218+
template <typename Class, typename... Extra>
219+
void execute(Class &cl, const Extra &...extra) && {
231220
#if defined(PYBIND11_CPP14)
232221
cl.def("__init__", [func = std::move(class_factory)]
233222
#else
234-
CFuncType &func = class_factory;
223+
auto &func = class_factory;
235224
cl.def("__init__", [func]
236225
#endif
237226
(value_and_holder &v_h, Args... args) {
238227
construct<Class>(v_h, func(std::forward<Args>(args)...),
239228
Py_TYPE(v_h.inst) != v_h.type->type);
240229
}, is_new_style_constructor(), extra...);
241230
}
231+
};
242232

243-
// Add __init__ definition for a class with an alias *and* distinct alias factory; the former is
244-
// called when the `self` type passed to `__init__` is the direct class (i.e. not inherited), the latter
245-
// when `self` is a Python-side subtype.
246-
template <typename Class, typename... Extra,
247-
enable_if_t<Class::has_alias && !std::is_void<AFuncIn>::value, int> = 0>
233+
// Specialization for py::init(Func, AliasFunc)
234+
template <typename CFunc, typename AFunc,
235+
typename CReturn, typename... CArgs, typename AReturn, typename... AArgs>
236+
struct factory<CFunc, AFunc, CReturn(CArgs...), AReturn(AArgs...)> {
237+
static_assert(sizeof...(CArgs) == sizeof...(AArgs),
238+
"pybind11::init(class_factory, alias_factory): class and alias factories "
239+
"must have identical argument signatures");
240+
static_assert(all_of<std::is_same<CArgs, AArgs>...>::value,
241+
"pybind11::init(class_factory, alias_factory): class and alias factories "
242+
"must have identical argument signatures");
243+
244+
remove_reference_t<CFunc> class_factory;
245+
remove_reference_t<AFunc> alias_factory;
246+
247+
factory(CFunc &&c, AFunc &&a)
248+
: class_factory(std::forward<CFunc>(c)), alias_factory(std::forward<AFunc>(a)) { }
249+
250+
// The class factory is called when the `self` type passed to `__init__` is the direct
251+
// class (i.e. not inherited), the alias factory when `self` is a Python-side subtype.
252+
template <typename Class, typename... Extra>
248253
void execute(Class &cl, const Extra&... extra) && {
254+
static_assert(Class::has_alias, "The two-argument version of `py::init()` can "
255+
"only be used if the class has an alias");
249256
#if defined(PYBIND11_CPP14)
250257
cl.def("__init__", [class_func = std::move(class_factory), alias_func = std::move(alias_factory)]
251258
#else
252-
CFuncType &class_func = class_factory;
253-
AFuncType &alias_func = alias_factory;
259+
auto &class_func = class_factory;
260+
auto &alias_func = alias_factory;
254261
cl.def("__init__", [class_func, alias_func]
255262
#endif
256-
(value_and_holder &v_h, Args... args) {
263+
(value_and_holder &v_h, CArgs... args) {
257264
if (Py_TYPE(v_h.inst) == v_h.type->type)
258265
// If the instance type equals the registered type we don't have inheritance, so
259266
// don't need the alias and can construct using the class function:
260-
construct<Class>(v_h, class_func(std::forward<Args>(args)...), false);
267+
construct<Class>(v_h, class_func(std::forward<CArgs>(args)...), false);
261268
else
262-
construct<Class>(v_h, alias_func(std::forward<Args>(args)...), true);
269+
construct<Class>(v_h, alias_func(std::forward<CArgs>(args)...), true);
263270
}, is_new_style_constructor(), extra...);
264271
}
265272
};
266273

267-
template <typename Func> using functype =
268-
conditional_t<std::is_function<remove_reference_t<Func>>::value, remove_reference_t<Func> *,
269-
conditional_t<is_function_pointer<remove_reference_t<Func>>::value, remove_reference_t<Func>,
270-
Func>>;
271-
272-
// Helper definition to infer the detail::initimpl::factory template types from a callable object
273-
template <typename Func, typename Return, typename... Args>
274-
factory<functype<Func>, void, Args...> func_decltype(Return (*)(Args...));
275-
276-
// metatemplate that ensures the Class and Alias factories take identical arguments: we need to be
277-
// able to call either one with the given arguments (depending on the final instance type).
278-
template <typename Return1, typename Return2, typename... Args1, typename... Args2>
279-
inline constexpr bool require_matching_arguments(Return1 (*)(Args1...), Return2 (*)(Args2...)) {
280-
static_assert(sizeof...(Args1) == sizeof...(Args2),
281-
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
282-
static_assert(all_of<std::is_same<Args1, Args2>...>::value,
283-
"pybind11::init(class_factory, alias_factory): class and alias factories must have identical argument signatures");
284-
return true;
285-
}
286-
287-
// Unimplemented function provided only for its type signature (via `decltype`), which resolves to
288-
// the appropriate specialization of the above `init` struct with the appropriate function, argument
289-
// and return types.
290-
template <typename CFunc, typename AFunc,
291-
typename CReturn, typename... CArgs, typename AReturn, typename... AArgs,
292-
bool = require_matching_arguments((CReturn (*)(CArgs...)) nullptr, (AReturn (*)(AArgs...)) nullptr)>
293-
factory<functype<CFunc>, functype<AFunc>, CArgs...> func_decltype(CReturn (*)(CArgs...), AReturn (*)(AArgs...));
294-
295-
// Resolves to the appropriate specialization of the `pybind11::detail::initimpl::factory<...>` for a
296-
// given init function or pair of class/alias init functions.
297-
template <typename... Func> using factory_t = decltype(func_decltype<Func...>(
298-
(function_signature_t<Func> *) nullptr...));
299-
300274
NAMESPACE_END(initimpl)
301275
NAMESPACE_END(detail)
302276
NAMESPACE_END(pybind11)

include/pybind11/pybind11.h

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1389,12 +1389,12 @@ template <typename... Args> detail::initimpl::constructor<Args...> init() { retu
13891389
template <typename... Args> detail::initimpl::alias_constructor<Args...> init_alias() { return {}; }
13901390

13911391
/// Binds a factory function as a constructor
1392-
template <typename Func, typename Ret = detail::initimpl::factory_t<Func>>
1392+
template <typename Func, typename Ret = detail::initimpl::factory<Func>>
13931393
Ret init(Func &&f) { return {std::forward<Func>(f)}; }
13941394

13951395
/// Dual-argument factory function: the first function is called when no alias is needed, the second
13961396
/// when an alias is needed (i.e. due to python-side inheritance). Arguments must be identical.
1397-
template <typename CFunc, typename AFunc, typename Ret = detail::initimpl::factory_t<CFunc, AFunc>>
1397+
template <typename CFunc, typename AFunc, typename Ret = detail::initimpl::factory<CFunc, AFunc>>
13981398
Ret init(CFunc &&c, AFunc &&a) {
13991399
return {std::forward<CFunc>(c), std::forward<AFunc>(a)};
14001400
}

0 commit comments

Comments
 (0)