Update OVERLOAD macros to support ref/ptr return type overloads

This adds a static local variable (in dead code unless actually needed)
in the overload code that is used for storage if the overload is for
some convert-by-value type (such as numeric values or std::string).

This has limitations (as written up in the advanced doc), but is better
than simply not being able to overload reference or pointer methods.
This commit is contained in:
Jason Rhinelander
2016-09-08 14:49:43 -04:00
parent 116d37c9ba
commit 7dfb932e70
5 changed files with 136 additions and 39 deletions

View File

@@ -867,14 +867,8 @@ template <typename type> using cast_is_temporary_value_reference = bool_constant
!std::is_base_of<type_caster_generic, make_caster<type>>::value
>;
NAMESPACE_END(detail)
template <typename T> T cast(const handle &handle) {
using type_caster = detail::make_caster<T>;
static_assert(!detail::cast_is_temporary_value_reference<T>::value,
"Unable to cast type to reference: value is local to type caster");
type_caster conv;
template <typename T> make_caster<T> load_type(const handle &handle) {
make_caster<T> conv;
if (!conv.load(handle, true)) {
#if defined(NDEBUG)
throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)");
@@ -883,7 +877,16 @@ template <typename T> T cast(const handle &handle) {
(std::string) handle.get_type().str() + " to C++ type '" + type_id<T>() + "''");
#endif
}
return conv.operator typename type_caster::template cast_op_type<T>();
return conv;
}
NAMESPACE_END(detail)
template <typename T> T cast(const handle &handle) {
static_assert(!detail::cast_is_temporary_value_reference<T>::value,
"Unable to cast type to reference: value is local to type caster");
using type_caster = detail::make_caster<T>;
return detail::load_type<T>(handle).operator typename type_caster::template cast_op_type<T>();
}
template <typename T> object cast(const T &value,
@@ -900,7 +903,7 @@ template <typename T> T handle::cast() const { return pybind11::cast<T>(*this);
template <> inline void handle::cast() const { return; }
template <typename T>
typename std::enable_if<detail::move_always<T>::value || detail::move_if_unreferenced<T>::value, T>::type move(object &&obj) {
detail::enable_if_t<detail::move_always<T>::value || detail::move_if_unreferenced<T>::value, T> move(object &&obj) {
if (obj.ref_count() > 1)
#if defined(NDEBUG)
throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references"
@@ -910,18 +913,8 @@ typename std::enable_if<detail::move_always<T>::value || detail::move_if_unrefer
" instance to C++ " + type_id<T>() + " instance: instance has multiple references");
#endif
typedef detail::type_caster<T> type_caster;
type_caster conv;
if (!conv.load(obj, true))
#if defined(NDEBUG)
throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)");
#else
throw cast_error("Unable to cast Python instance of type " +
(std::string) obj.get_type().str() + " to C++ type '" + type_id<T>() + "''");
#endif
// Move into a temporary and return that, because the reference may be a local value of `conv`
T ret = std::move(conv.operator T&());
T ret = std::move(detail::load_type<T>(obj).operator T&());
return ret;
}
@@ -930,24 +923,57 @@ typename std::enable_if<detail::move_always<T>::value || detail::move_if_unrefer
// object has multiple references, but trying to copy will fail to compile.
// - If both movable and copyable, check ref count: if 1, move; otherwise copy
// - Otherwise (not movable), copy.
template <typename T> typename std::enable_if<detail::move_always<T>::value, T>::type cast(object &&object) {
template <typename T> detail::enable_if_t<detail::move_always<T>::value, T> cast(object &&object) {
return move<T>(std::move(object));
}
template <typename T> typename std::enable_if<detail::move_if_unreferenced<T>::value, T>::type cast(object &&object) {
template <typename T> detail::enable_if_t<detail::move_if_unreferenced<T>::value, T> cast(object &&object) {
if (object.ref_count() > 1)
return cast<T>(object);
else
return move<T>(std::move(object));
}
template <typename T> typename std::enable_if<detail::move_never<T>::value, T>::type cast(object &&object) {
template <typename T> detail::enable_if_t<detail::move_never<T>::value, T> cast(object &&object) {
return cast<T>(object);
}
// Provide a ref_cast() with move support for objects (only participates for moveable types)
template <typename T> detail::enable_if_t<detail::move_is_plain_type<T>::value, T>
ref_cast(object &&object) { return cast<T>(std::move(object)); }
template <typename T> T object::cast() const & { return pybind11::cast<T>(*this); }
template <typename T> T object::cast() && { return pybind11::cast<T>(std::move(*this)); }
template <> inline void object::cast() const & { return; }
template <> inline void object::cast() && { return; }
NAMESPACE_BEGIN(detail)
struct overload_nothing {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro
template <typename ret_type> using overload_local_t = conditional_t<
cast_is_temporary_value_reference<ret_type>::value, intrinsic_t<ret_type>, overload_nothing>;
template <typename T> enable_if_t<std::is_lvalue_reference<T>::value, T> storage_cast(intrinsic_t<T> &v) { return v; }
template <typename T> enable_if_t<std::is_pointer<T>::value, T> storage_cast(intrinsic_t<T> &v) { return &v; }
// Trampoline use: for reference/pointer types to value-converted values, we do a value cast, then
// store the result in the given variable. For other types, this is a no-op.
template <typename T> enable_if_t<cast_is_temporary_value_reference<T>::value, T> cast_ref(object &&o, intrinsic_t<T> &storage) {
using type_caster = make_caster<T>;
using itype = intrinsic_t<T>;
storage = std::move(load_type<T>(o).operator typename type_caster::template cast_op_type<itype>());
return storage_cast<T>(storage);
}
template <typename T> enable_if_t<!cast_is_temporary_value_reference<T>::value, T> cast_ref(object &&, overload_nothing &) {
pybind11_fail("Internal error: cast_ref fallback invoked"); }
// Trampoline use: Having a pybind11::cast with an invalid reference type is going to static_assert, even
// though if it's in dead code, so we provide a "trampoline" to pybind11::cast that only does anything in
// cases where pybind11::cast is valid.
template <typename T> enable_if_t<!cast_is_temporary_value_reference<T>::value, T> cast_safe(object &&o) {
return pybind11::cast<T>(std::move(o)); }
template <typename T> enable_if_t<cast_is_temporary_value_reference<T>::value, T> cast_safe(object &&) {
pybind11_fail("Internal error: cast_safe fallback invoked"); }
template <> inline void cast_safe<void>(object &&) {}
NAMESPACE_END(detail)
template <return_value_policy policy = return_value_policy::automatic_reference,

View File

@@ -1485,8 +1485,15 @@ template <class T> function get_overload(const T *this_ptr, const char *name) {
#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) { \
pybind11::gil_scoped_acquire gil; \
pybind11::function overload = pybind11::get_overload(static_cast<const cname *>(this), name); \
if (overload) \
return overload(__VA_ARGS__).template cast<ret_type>(); }
if (overload) { \
pybind11::object o = overload(__VA_ARGS__); \
if (pybind11::detail::cast_is_temporary_value_reference<ret_type>::value) { \
static pybind11::detail::overload_local_t<ret_type> local_value; \
return pybind11::detail::cast_ref<ret_type>(std::move(o), local_value); \
} \
else return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
} \
}
#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \
PYBIND11_OVERLOAD_INT(ret_type, cname, name, __VA_ARGS__) \