py::cast variant using type_info instead of template argument for non-virtual types.
Issue description
It would be a nice feature to have a non-template casting function using type_info for non-virtual types. I gather pybind11 for static-types binds the type_info to the first export of the type by ptr (if it was a Base it gets locked in Base, if it was a Derived stays a Derived, etc)
I made a workaround by accessing the detail part of pybind, so I guess it could change eventually.
Use case
In C++ I have dynamic-bound factories wrappers like
Base *Create( int id )
T *Create<T>() { return static_cast<T*>(Create( id_map[typeid(T)] )); }.
To avoid having to make one py-binding for each type (non-scalable), I export a
py::object Create(int id)
that changes the return type according to a type_info.
Setup
pybind11 2.2.1
Example code
DIRECT TYPE
py::object castComponentByTypeInfo(Base *src, const std::type_info &type) {
using T1 = pybind11::detail::type_caster_generic;
auto &st = T1::src_and_type(src, type, nullptr);
assert(st.second != nullptr);
auto handle = T1::cast(
st.first, py::return_value_policy::reference, {}, st.second,
nullptr, nullptr
);
return py::reinterpret_borrow<py::object>(handle);
}
HELD TYPE
py::object castComponentByTypeInfo(holder<Base> src, const std::type_info &type) {
using T1 = pybind11::detail::type_caster_generic;
auto &st = T1::src_and_type(src.get(), type, nullptr);
assert(st.second != nullptr);
auto handle = T1::cast(
st.first, py::return_value_policy::take_ownership, {}, st.second,
nullptr, nullptr, &src);
return py::reinterpret_steal<py::object>(handle);
}
#define HAVE_ROUND
#include <pybind11/pybind11.h>
#include <iostream>
#include <stdexcept>
//////////////
// TYPES
//////////////
class C {
public:
int something;
};
template<typename T>
struct LousyPool {
static T poolbuf[1];
static int poolver[1];
};
C LousyPool<C>::poolbuf[1] {0};
int LousyPool<C>::poolver[1] {0};
//////////////
// HOLDER TYPE
//////////////
template<typename T>
class Holder {
public:
int index;
int version;
Holder() : index(-1), version(-1) {}
Holder(int index, int version) : index(index), version(version) {}
Holder(const Holder& o) : index(o.index), version(o.version) {}
// We don't want this constructor to be called from Python
explicit Holder(const T* junk) { throw std::exception("Never called"); }
T *get() const {
std::cerr << "GET " << this << " " << index << " " << version << " " << LousyPool<T>::poolver[index] << std::endl;
// Check if the version matches
if (version != LousyPool<T>::poolver[index]) {
throw std::exception(("Version mismatch, dangling ref version " + std::to_string(version) + " current one " + std::to_string(LousyPool<T>::poolver[index])).c_str());
}
return LousyPool<T>::poolbuf + index;
}
};
// Declare the custom holder type for pybind11
PYBIND11_DECLARE_HOLDER_TYPE(T, Holder<T>);
//////////////
// FUNCS
//////////////
Holder<C> c_acquire() {
auto p = Holder<C>(0, LousyPool<C>::poolver[0]);
p.get()->something = 123;
return p;
}
int c_touch(Holder<C> p) {
return p.get()->something;
}
void c_release(Holder<C> p) {
// Increment the version to simulate object state change
LousyPool<C>::poolver[p.index]++;
}
//////////////
// BINDINGS
//////////////
PYBIND11_MODULE(pybind11_bugs_lib, m) {
pybind11::class_<C, Holder<C>>(m, "C")
.def_readwrite("something", &C::something)
;
m.def("c_acquire", &c_acquire);
m.def("c_release", &c_release);
m.def("c_touch", &c_touch);
}