mirror of
https://github.com/pybind/pybind11.git
synced 2024-11-11 08:03:55 +00:00
Merge pull request #400 from jagerman/add-ref-virtual-macros
Add a way to deal with copied value references
This commit is contained in:
commit
f22683806e
@ -298,13 +298,11 @@ helper class that is defined as follows:
|
|||||||
|
|
||||||
The macro :func:`PYBIND11_OVERLOAD_PURE` should be used for pure virtual
|
The macro :func:`PYBIND11_OVERLOAD_PURE` should be used for pure virtual
|
||||||
functions, and :func:`PYBIND11_OVERLOAD` should be used for functions which have
|
functions, and :func:`PYBIND11_OVERLOAD` should be used for functions which have
|
||||||
a default implementation.
|
a default implementation. There are also two alternate macros
|
||||||
|
:func:`PYBIND11_OVERLOAD_PURE_NAME` and :func:`PYBIND11_OVERLOAD_NAME` which
|
||||||
There are also two alternate macros :func:`PYBIND11_OVERLOAD_PURE_NAME` and
|
take a string-valued name argument between the *Parent class* and *Name of the
|
||||||
:func:`PYBIND11_OVERLOAD_NAME` which take a string-valued name argument between
|
function* slots. This is useful when the C++ and Python versions of the
|
||||||
the *Parent class* and *Name of the function* slots. This is useful when the
|
function have different names, e.g. ``operator()`` vs ``__call__``.
|
||||||
C++ and Python versions of the function have different names, e.g.
|
|
||||||
``operator()`` vs ``__call__``.
|
|
||||||
|
|
||||||
The binding code also needs a few minor adaptations (highlighted):
|
The binding code also needs a few minor adaptations (highlighted):
|
||||||
|
|
||||||
@ -327,10 +325,10 @@ The binding code also needs a few minor adaptations (highlighted):
|
|||||||
return m.ptr();
|
return m.ptr();
|
||||||
}
|
}
|
||||||
|
|
||||||
Importantly, pybind11 is made aware of the trampoline trampoline helper class
|
Importantly, pybind11 is made aware of the trampoline helper class by
|
||||||
by specifying it as an extra template argument to :class:`class_`. (This can
|
specifying it as an extra template argument to :class:`class_`. (This can also
|
||||||
also be combined with other template arguments such as a custom holder type;
|
be combined with other template arguments such as a custom holder type; the
|
||||||
the order of template types does not matter). Following this, we are able to
|
order of template types does not matter). Following this, we are able to
|
||||||
define a constructor as usual.
|
define a constructor as usual.
|
||||||
|
|
||||||
Note, however, that the above is sufficient for allowing python classes to
|
Note, however, that the above is sufficient for allowing python classes to
|
||||||
@ -357,6 +355,25 @@ a virtual method call.
|
|||||||
|
|
||||||
Please take a look at the :ref:`macro_notes` before using this feature.
|
Please take a look at the :ref:`macro_notes` before using this feature.
|
||||||
|
|
||||||
|
.. note::
|
||||||
|
|
||||||
|
When the overridden type returns a reference or pointer to a type that
|
||||||
|
pybind11 converts from Python (for example, numeric values, std::string,
|
||||||
|
and other built-in value-converting types), there are some limitations to
|
||||||
|
be aware of:
|
||||||
|
|
||||||
|
- because in these cases there is no C++ variable to reference (the value
|
||||||
|
is stored in the referenced Python variable), pybind11 provides one in
|
||||||
|
the PYBIND11_OVERLOAD macros (when needed) with static storage duration.
|
||||||
|
Note that this means that invoking the overloaded method on *any*
|
||||||
|
instance will change the referenced value stored in *all* instances of
|
||||||
|
that type.
|
||||||
|
|
||||||
|
- Attempts to modify a non-const reference will not have the desired
|
||||||
|
effect: it will change only the static cache variable, but this change
|
||||||
|
will not propagate to underlying Python instance, and the change will be
|
||||||
|
replaced the next time the overload is invoked.
|
||||||
|
|
||||||
.. seealso::
|
.. seealso::
|
||||||
|
|
||||||
The file :file:`tests/test_virtual_functions.cpp` contains a complete
|
The file :file:`tests/test_virtual_functions.cpp` contains a complete
|
||||||
|
@ -863,14 +863,8 @@ template <typename type> using cast_is_temporary_value_reference = bool_constant
|
|||||||
!std::is_base_of<type_caster_generic, make_caster<type>>::value
|
!std::is_base_of<type_caster_generic, make_caster<type>>::value
|
||||||
>;
|
>;
|
||||||
|
|
||||||
|
// Basic python -> C++ casting; throws if casting fails
|
||||||
NAMESPACE_END(detail)
|
template <typename TypeCaster> TypeCaster &load_type(TypeCaster &conv, const handle &handle) {
|
||||||
|
|
||||||
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;
|
|
||||||
if (!conv.load(handle, true)) {
|
if (!conv.load(handle, true)) {
|
||||||
#if defined(NDEBUG)
|
#if defined(NDEBUG)
|
||||||
throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)");
|
throw cast_error("Unable to cast Python instance to C++ type (compile in debug mode for details)");
|
||||||
@ -879,7 +873,22 @@ template <typename T> T cast(const handle &handle) {
|
|||||||
(std::string) handle.get_type().str() + " to C++ type '" + type_id<T>() + "''");
|
(std::string) handle.get_type().str() + " to C++ type '" + type_id<T>() + "''");
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
return conv.operator typename type_caster::template cast_op_type<T>();
|
return conv;
|
||||||
|
}
|
||||||
|
// Wrapper around the above that also constructs and returns a type_caster
|
||||||
|
template <typename T> make_caster<T> load_type(const handle &handle) {
|
||||||
|
make_caster<T> conv;
|
||||||
|
load_type(conv, handle);
|
||||||
|
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,
|
template <typename T> object cast(const T &value,
|
||||||
@ -896,7 +905,7 @@ template <typename T> T handle::cast() const { return pybind11::cast<T>(*this);
|
|||||||
template <> inline void handle::cast() const { return; }
|
template <> inline void handle::cast() const { return; }
|
||||||
|
|
||||||
template <typename T>
|
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 (obj.ref_count() > 1)
|
||||||
#if defined(NDEBUG)
|
#if defined(NDEBUG)
|
||||||
throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references"
|
throw cast_error("Unable to cast Python instance to C++ rvalue: instance has multiple references"
|
||||||
@ -906,18 +915,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");
|
" instance to C++ " + type_id<T>() + " instance: instance has multiple references");
|
||||||
#endif
|
#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`
|
// 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;
|
return ret;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -926,16 +925,16 @@ 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.
|
// 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
|
// - If both movable and copyable, check ref count: if 1, move; otherwise copy
|
||||||
// - Otherwise (not movable), 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));
|
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)
|
if (object.ref_count() > 1)
|
||||||
return cast<T>(object);
|
return cast<T>(object);
|
||||||
else
|
else
|
||||||
return move<T>(std::move(object));
|
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);
|
return cast<T>(object);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -944,6 +943,30 @@ template <typename T> T object::cast() && { return pybind11::cast<T>(std::move(*
|
|||||||
template <> inline void object::cast() const & { return; }
|
template <> inline void object::cast() const & { return; }
|
||||||
template <> inline void object::cast() && { return; }
|
template <> inline void object::cast() && { return; }
|
||||||
|
|
||||||
|
NAMESPACE_BEGIN(detail)
|
||||||
|
|
||||||
|
struct overload_unused {}; // Placeholder type for the unneeded (and dead code) static variable in the OVERLOAD_INT macro
|
||||||
|
template <typename ret_type> using overload_caster_t = conditional_t<
|
||||||
|
cast_is_temporary_value_reference<ret_type>::value, make_caster<ret_type>, overload_unused>;
|
||||||
|
|
||||||
|
// 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, make_caster<T> &caster) {
|
||||||
|
return load_type(caster, o).operator typename make_caster<T>::template cast_op_type<T>();
|
||||||
|
}
|
||||||
|
template <typename T> enable_if_t<!cast_is_temporary_value_reference<T>::value, T> cast_ref(object &&, overload_unused &) {
|
||||||
|
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,
|
template <return_value_policy policy = return_value_policy::automatic_reference,
|
||||||
|
@ -1489,8 +1489,15 @@ template <class T> function get_overload(const T *this_ptr, const char *name) {
|
|||||||
#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) { \
|
#define PYBIND11_OVERLOAD_INT(ret_type, cname, name, ...) { \
|
||||||
pybind11::gil_scoped_acquire gil; \
|
pybind11::gil_scoped_acquire gil; \
|
||||||
pybind11::function overload = pybind11::get_overload(static_cast<const cname *>(this), name); \
|
pybind11::function overload = pybind11::get_overload(static_cast<const cname *>(this), name); \
|
||||||
if (overload) \
|
if (overload) { \
|
||||||
return overload(__VA_ARGS__).template cast<ret_type>(); }
|
auto o = overload(__VA_ARGS__); \
|
||||||
|
if (pybind11::detail::cast_is_temporary_value_reference<ret_type>::value) { \
|
||||||
|
static pybind11::detail::overload_caster_t<ret_type> caster; \
|
||||||
|
return pybind11::detail::cast_ref<ret_type>(std::move(o), caster); \
|
||||||
|
} \
|
||||||
|
else return pybind11::detail::cast_safe<ret_type>(std::move(o)); \
|
||||||
|
} \
|
||||||
|
}
|
||||||
|
|
||||||
#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \
|
#define PYBIND11_OVERLOAD_NAME(ret_type, cname, name, fn, ...) \
|
||||||
PYBIND11_OVERLOAD_INT(ret_type, cname, name, __VA_ARGS__) \
|
PYBIND11_OVERLOAD_INT(ret_type, cname, name, __VA_ARGS__) \
|
||||||
|
@ -21,14 +21,22 @@ public:
|
|||||||
|
|
||||||
virtual int run(int value) {
|
virtual int run(int value) {
|
||||||
py::print("Original implementation of "
|
py::print("Original implementation of "
|
||||||
"ExampleVirt::run(state={}, value={})"_s.format(state, value));
|
"ExampleVirt::run(state={}, value={}, str1={}, str2={})"_s.format(state, value, get_string1(), *get_string2()));
|
||||||
return state + value;
|
return state + value;
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool run_bool() = 0;
|
virtual bool run_bool() = 0;
|
||||||
virtual void pure_virtual() = 0;
|
virtual void pure_virtual() = 0;
|
||||||
|
|
||||||
|
// Returning a reference/pointer to a type converted from python (numbers, strings, etc.) is a
|
||||||
|
// bit trickier, because the actual int& or std::string& or whatever only exists temporarily, so
|
||||||
|
// we have to handle it specially in the trampoline class (see below).
|
||||||
|
virtual const std::string &get_string1() { return str1; }
|
||||||
|
virtual const std::string *get_string2() { return &str2; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
int state;
|
int state;
|
||||||
|
const std::string str1{"default1"}, str2{"default2"};
|
||||||
};
|
};
|
||||||
|
|
||||||
/* This is a wrapper class that must be generated */
|
/* This is a wrapper class that must be generated */
|
||||||
@ -36,7 +44,7 @@ class PyExampleVirt : public ExampleVirt {
|
|||||||
public:
|
public:
|
||||||
using ExampleVirt::ExampleVirt; /* Inherit constructors */
|
using ExampleVirt::ExampleVirt; /* Inherit constructors */
|
||||||
|
|
||||||
virtual int run(int value) {
|
int run(int value) override {
|
||||||
/* Generate wrapping code that enables native function overloading */
|
/* Generate wrapping code that enables native function overloading */
|
||||||
PYBIND11_OVERLOAD(
|
PYBIND11_OVERLOAD(
|
||||||
int, /* Return type */
|
int, /* Return type */
|
||||||
@ -46,7 +54,7 @@ public:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual bool run_bool() {
|
bool run_bool() override {
|
||||||
PYBIND11_OVERLOAD_PURE(
|
PYBIND11_OVERLOAD_PURE(
|
||||||
bool, /* Return type */
|
bool, /* Return type */
|
||||||
ExampleVirt, /* Parent class */
|
ExampleVirt, /* Parent class */
|
||||||
@ -56,7 +64,7 @@ public:
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual void pure_virtual() {
|
void pure_virtual() override {
|
||||||
PYBIND11_OVERLOAD_PURE(
|
PYBIND11_OVERLOAD_PURE(
|
||||||
void, /* Return type */
|
void, /* Return type */
|
||||||
ExampleVirt, /* Parent class */
|
ExampleVirt, /* Parent class */
|
||||||
@ -65,6 +73,27 @@ public:
|
|||||||
in the previous line is needed for some compilers */
|
in the previous line is needed for some compilers */
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// We can return reference types for compatibility with C++ virtual interfaces that do so, but
|
||||||
|
// note they have some significant limitations (see the documentation).
|
||||||
|
const std::string &get_string1() override {
|
||||||
|
PYBIND11_OVERLOAD(
|
||||||
|
const std::string &, /* Return type */
|
||||||
|
ExampleVirt, /* Parent class */
|
||||||
|
get_string1, /* Name of function */
|
||||||
|
/* (no arguments) */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const std::string *get_string2() override {
|
||||||
|
PYBIND11_OVERLOAD(
|
||||||
|
const std::string *, /* Return type */
|
||||||
|
ExampleVirt, /* Parent class */
|
||||||
|
get_string2, /* Name of function */
|
||||||
|
/* (no arguments) */
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class NonCopyable {
|
class NonCopyable {
|
||||||
@ -107,11 +136,11 @@ public:
|
|||||||
};
|
};
|
||||||
class NCVirtTrampoline : public NCVirt {
|
class NCVirtTrampoline : public NCVirt {
|
||||||
#if !defined(__INTEL_COMPILER)
|
#if !defined(__INTEL_COMPILER)
|
||||||
virtual NonCopyable get_noncopyable(int a, int b) {
|
NonCopyable get_noncopyable(int a, int b) override {
|
||||||
PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b);
|
PYBIND11_OVERLOAD(NonCopyable, NCVirt, get_noncopyable, a, b);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
virtual Movable get_movable(int a, int b) {
|
Movable get_movable(int a, int b) override {
|
||||||
PYBIND11_OVERLOAD_PURE(Movable, NCVirt, get_movable, a, b);
|
PYBIND11_OVERLOAD_PURE(Movable, NCVirt, get_movable, a, b);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
@ -20,13 +20,23 @@ def test_override(capture, msg):
|
|||||||
print('ExtendedExampleVirt::run_bool()')
|
print('ExtendedExampleVirt::run_bool()')
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
def get_string1(self):
|
||||||
|
return "override1"
|
||||||
|
|
||||||
def pure_virtual(self):
|
def pure_virtual(self):
|
||||||
print('ExtendedExampleVirt::pure_virtual(): %s' % self.data)
|
print('ExtendedExampleVirt::pure_virtual(): %s' % self.data)
|
||||||
|
|
||||||
|
class ExtendedExampleVirt2(ExtendedExampleVirt):
|
||||||
|
def __init__(self, state):
|
||||||
|
super(ExtendedExampleVirt2, self).__init__(state + 1)
|
||||||
|
|
||||||
|
def get_string2(self):
|
||||||
|
return "override2"
|
||||||
|
|
||||||
ex12 = ExampleVirt(10)
|
ex12 = ExampleVirt(10)
|
||||||
with capture:
|
with capture:
|
||||||
assert runExampleVirt(ex12, 20) == 30
|
assert runExampleVirt(ex12, 20) == 30
|
||||||
assert capture == "Original implementation of ExampleVirt::run(state=10, value=20)"
|
assert capture == "Original implementation of ExampleVirt::run(state=10, value=20, str1=default1, str2=default2)"
|
||||||
|
|
||||||
with pytest.raises(RuntimeError) as excinfo:
|
with pytest.raises(RuntimeError) as excinfo:
|
||||||
runExampleVirtVirtual(ex12)
|
runExampleVirtVirtual(ex12)
|
||||||
@ -37,7 +47,7 @@ def test_override(capture, msg):
|
|||||||
assert runExampleVirt(ex12p, 20) == 32
|
assert runExampleVirt(ex12p, 20) == 32
|
||||||
assert capture == """
|
assert capture == """
|
||||||
ExtendedExampleVirt::run(20), calling parent..
|
ExtendedExampleVirt::run(20), calling parent..
|
||||||
Original implementation of ExampleVirt::run(state=11, value=21)
|
Original implementation of ExampleVirt::run(state=11, value=21, str1=override1, str2=default2)
|
||||||
"""
|
"""
|
||||||
with capture:
|
with capture:
|
||||||
assert runExampleVirtBool(ex12p) is False
|
assert runExampleVirtBool(ex12p) is False
|
||||||
@ -46,11 +56,19 @@ def test_override(capture, msg):
|
|||||||
runExampleVirtVirtual(ex12p)
|
runExampleVirtVirtual(ex12p)
|
||||||
assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"
|
assert capture == "ExtendedExampleVirt::pure_virtual(): Hello world"
|
||||||
|
|
||||||
|
ex12p2 = ExtendedExampleVirt2(15)
|
||||||
|
with capture:
|
||||||
|
assert runExampleVirt(ex12p2, 50) == 68
|
||||||
|
assert capture == """
|
||||||
|
ExtendedExampleVirt::run(50), calling parent..
|
||||||
|
Original implementation of ExampleVirt::run(state=17, value=51, str1=override1, str2=override2)
|
||||||
|
"""
|
||||||
|
|
||||||
cstats = ConstructorStats.get(ExampleVirt)
|
cstats = ConstructorStats.get(ExampleVirt)
|
||||||
assert cstats.alive() == 2
|
assert cstats.alive() == 3
|
||||||
del ex12, ex12p
|
del ex12, ex12p, ex12p2
|
||||||
assert cstats.alive() == 0
|
assert cstats.alive() == 0
|
||||||
assert cstats.values() == ['10', '11']
|
assert cstats.values() == ['10', '11', '17']
|
||||||
assert cstats.copy_constructions == 0
|
assert cstats.copy_constructions == 0
|
||||||
assert cstats.move_constructions >= 0
|
assert cstats.move_constructions >= 0
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user