I'm trying to use a simple virtual function but I'm running into some weird problems what seems like undefined behaviour. I have stripped everything from my code to only reproduce the problem:
#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
namespace py = pybind11;
struct vec3
{
double t, x, y;
vec3(double t, double x, double y) : t(t), x(x), y(y) {}
};
class Point : public vec3
{
public:
Point(double t, double x, double y) : vec3(t, x, y) {}
virtual void test() {} // if this is virtual the problem occurs!
};
PYBIND11_MODULE(relatpy, m)
{
py::class_<vec3>(m, "vec3")
.def(py::init<double, double, double>())
.def_readwrite("t", &vec3::t)
.def_readwrite("x", &vec3::x)
.def_readwrite("y", &vec3::y);
py::class_<Point, vec3, PyPoint>(m, "Point")
.def(py::init<double, double, double>());
}
The virtual function test
doesn't do anything but if I remove the virtual
the problem goes away. I have no idea why this is happening but when I run the following python code:
from relatpy import *
p = Point(1, 2, 3) # t x y
print(p.t)
print(p.x)
print(p.y)
# should print 1 2 3 but prints JUNK 1 2
as the comment says I expect 1, 2, 3 but get some junk, 1, 2.
EDIT: As @273K pointed out in the comments this JUNK is the vtable pointer, although I'm not sure why it is ending up in the t
variable. So any help on how to fix this is appreciated!
I expect the output 1 2 3 but only when test
is virtual
do I get "JUNK, 1, 2". I have tried implementing a trampoline class that implemented the virtual void test
but this did not seem to solve the problem. Here is the code anyway in case I did something wrong:
for the trampoline class
class PyPoint : public Point
{
public:
/* Inherit the constructors */
using Point::Point;
/* Trampoline (need one for each virtual function) */
void test() override { PYBIND11_OVERRIDE_PURE(void, Point, test); }
};
and the bindings I changed:
py::class_<Point, vec3, PyPoint>(m, "Point")
.def(py::init<double, double, double>())
.def("test", &Point::test);
As @273K suggested in the comments I had to add t, x, y in de pybind bindings as follows:
py::class_<Point, vec3, PyPoint>(m, "Point")
.def(py::init<double, double, double>())
.def("test", &Point::test);
.def_readwrite("t", &Point::t)
.def_readwrite("x", &Point::x)
.def_readwrite("y", &Point::y);
It seems both &Point::t
and &vec3::t
work, not sure if there is a difference.