-
Notifications
You must be signed in to change notification settings - Fork 2.1k
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Can't use holders around types with custom converters #787
Comments
I don't see a nice way to implement this for all casters. One possible idea for thought: type casters could opt-in to supporting casting to/from holders by providing |
@jagerman, I rebuild my code against latest master branch with your commit and static asserts didn't triggered. I think that's because I don't actually specify class holder. I use custom casting directly for Here you can see what I do: url |
(URL tag arguments flipped; I do that all the time). |
Ah, you have your own type caster for a shared_ptr specifically, which is going to bypass the built-in generic holder casters I added the static asserts to. |
I think it's because of this part: operator sp_array*() { return &value; }
operator sp_array&() { return value; }
operator Array*() { return value.get(); }
operator Array&() { return *value; }
template <typename _T> using cast_op_type = pybind11::detail::cast_op_type<_T>; so pybind could be getting a raw pointer to the shared_ptr, and trying to wrap that in a unique pointer. I think you can change this to just: operator sp_array() { return value; }
template <typename> using cast_op_type = sp_array; to just make a copy of the shared pointer (rather than a reference or pointer) when it needs to be accessed.
|
(Moving discussion back to #785). A resolution to this bug (#787) would, I believe, make any registered holder work, so that when pybind tries to bind a holder type to a non-class type (e.g. a built-in string, Eigen array, or a user-provided custom converter) it would use the holder type to load the custom caster. (At least, that's what I have in mind in my suggestion above; we might go in some totally different direction, too). |
BTW I'm in process of refactoring |
Yes. The |
While moving on with Python bindings for my class hierarchy I stumbled over another difficulty that I think is related to this discussion topic. All my types are derived from base class Now the question is: how can I provide a fallback type caster that will allow me to pass any Python type to a function, taking What I want: I have a function I can provide an implementation of class py_anyobj : public objbase {
protected:
py::object pyobj_;
public:
// inherit ctors
using objbase::objbase;
py_anyobj(const py::object& o) : pyobj_(o) {}
py_anyobj(py::handle h) : pyobj_{ py::reinterpret_steal<py::object>(h) } {}
py::object& pyobj() { return pyobj_; }
const py::object& pyobj() const { return pyobj_; }
// cast to py::object
operator py::object() { return pyobj_; }
operator py::object*() { return &pyobj_; }
}; Then, I can make custom template<> struct type_caster<std::shared_ptr<py_anyobj>> {
private:
using sp_anyobj = std::shared_ptr<py_anyobj>;
sp_anyobj value;
public:
static handle cast(const sp_anyobj& src, return_value_policy, handle) {
if(!src) return none().release();
return src->pyobj().release();
}
bool load(handle src, bool convert) {
value = std::make_shared<py_anyobj>(src);
return true;
}
static PYBIND11_DESCR name() {
return type_descr(_("py_anyobj"));
}
operator sp_anyobj() { return value; }
template <typename> using cast_op_type = sp_anyobj;
}; But then what? OK, now I can pass any Python type to function taking A nice addition would be backward C++ -> Python auto-convertion, i.e. when I return I had a semi-solution to this with |
You'll want to make #include <pybind11/pybind11.h>
namespace py = pybind11;
class B {
public:
virtual std::string say() { return "hi from B"; }
};
class A : public B {
public:
std::string say() override { return "hi from A"; }
};
class PyB : public B {
public:
using B::B;
PyB() = default;
PyB(py::object o) : myobj{o} { py::print("(PyB constructed from py::object)"); }
std::string say() override { return "hi from a pyobject: " + (std::string) py::str(myobj); }
private:
py::object myobj;
};
PYBIND11_PLUGIN(t787) {
py::module m("t787");
py::class_<B, PyB, std::shared_ptr<B>>(m, "B")
.def(py::init<>())
.def(py::init_alias<py::object>())
;
py::class_<A, B, std::shared_ptr<A>>(m, "A")
.def(py::init<>())
;
py::implicitly_convertible<py::object, B>();
m.def("foo", [](std::shared_ptr<B> b) { return "foo says: " + b->say(); });
return m.ptr();
} Edit: oops, forgot the output: >>> from t787 import A, B, foo
>>> print(foo(B()))
foo says: hi from B
>>> print(foo(A()))
foo says: hi from A
>>> print(foo([1, 2, 3]))
(PyB constructed from py::object)
foo says: hi from a pyobject: [1, 2, 3] |
One more situation considering the topic.
Code. #include <pybind11/pybind11.h>
#include <iostream>
namespace py = pybind11;
using namespace pybind11::literals;
/*-----------------------------------------------------------------------------
* objbase
*-----------------------------------------------------------------------------*/
class objbase : public std::enable_shared_from_this< objbase > {
int prop_ = 42;
public:
virtual std::string name() const {
return "objbase";
}
int prop() const { return prop_; }
};
using sp_obj = std::shared_ptr<objbase>;
using sp_cobj = std::shared_ptr<const objbase>;
// trampoline
template<class T = objbase>
class py_objbase : public T {
public:
using T::T;
std::string name() const override {
PYBIND11_OVERLOAD(std::string, T, name);
}
};
/*-----------------------------------------------------------------------------
* mytype_simple
*-----------------------------------------------------------------------------*/
class mytype_simple : public objbase {
public:
long value;
using objbase::objbase;
mytype_simple(long value_) : value(value_) {}
std::string name() const override {
return "mytype_simple";
}
};
// custom type cast
NAMESPACE_BEGIN(pybind11)
NAMESPACE_BEGIN(detail)
template<> struct type_caster<std::shared_ptr<mytype_simple>> {
std::shared_ptr<mytype_simple> value;
static PYBIND11_DESCR name() {
return type_descr(_("mytype_simple"));
}
bool load(handle src, bool) {
/* Extract PyObject from handle */
PyObject *source = src.ptr();
/* Try converting into a Python integer value */
PyObject *tmp = PyNumber_Long(source);
if (!tmp)
return false;
/* Now try to convert into a C++ int */
value = std::make_shared<mytype_simple>(PyLong_AsLong(tmp));
Py_DECREF(tmp);
/* Ensure return code was OK (to avoid out-of-range errors etc) */
return !PyErr_Occurred();
}
static handle cast(const std::shared_ptr<mytype_simple>& src, return_value_policy, handle) {
return PyLong_FromLong(src->value);
}
operator std::shared_ptr<mytype_simple>() { return value; }
template< typename > using cast_op_type = std::shared_ptr<mytype_simple>;
};
NAMESPACE_END(detail)
NAMESPACE_END(pybind11)
PYBIND11_PLUGIN(example) {
py::module m("example");
// bind objbase
py::class_<objbase, py_objbase<>, std::shared_ptr<objbase>>(m, "objbase")
.def(py::init_alias<>())
.def("name", &objbase::name)
;
m.def("test_objbase_pass", [](std::shared_ptr<objbase> obj) {
std::cout << "Am I mytype_simple instance? " << std::boolalpha
<< bool(std::dynamic_pointer_cast<mytype_simple>(obj)) << std::endl;
});
m.def("test_mytype_pass", [](std::shared_ptr<mytype_simple> obj) {
std::cout << "Am I objbase instance? " << std::boolalpha
<< bool(std::dynamic_pointer_cast<objbase>(obj)) << std::endl;
});
} Python test: In [1]: import example as e
In [2]: e.test_mytype_pass(42L)
Am I objbase instance? true
In [3]: e.test_objbase_pass(42L)
---------------------------------------------------------------------------
TypeError Traceback (most recent call last)
<ipython-input-3-304ca3cb5cbc> in <module>()
----> 1 e.test_objbase_pass(42L)
TypeError: test_objbase_pass(): incompatible function arguments. The following argument types are supported:
1. (arg0: example.objbase) -> None
Invoked with: 42L obviously not working. All I can do is to add implicit cast: py::implicitly_convertible< std::shared_ptr<mytype_simple>, objbase >(); But with this cast Is there any solution to this? |
@jagerman I was looking into fixing this as I am attempting to use a |
Created an attempted solution for this in #1985. |
I think I'm running into this issue with I'm using it in the context of the Binder binding generator, which really wants to write bindings for any This seems to result in Am I actually running into this issue? Is |
This fails for any
T
with a custom type converter (e.g.std::string
,Eigen::MatrixXd
,MyCustomConvertingType
, etc.):The issue is that the holder type casters assume that
type_caster<T>
is atype_caster_base<T>
, and make use oftype_caster_base<T>
constructors and internals. #786 makes it into a compile-time failure (before that it will result in strange run-time failures as we end up invoking a generic caster on an unregistered type).The text was updated successfully, but these errors were encountered: