c++abstract-classpybind11

pybind11 binding of a function with virtual class as argument or return type (solved)


here is an example of a code where i have a virtual class A, with its child A_2 and other classes/functions that have A as argument/return type. In use i could use instead A_2, A_3 ...


#include <pybind11/pybind11.h>
#include <pybind11/stl.h>
#include <iostream>

namespace py = pybind11;

class A {
    public:
        float i;
        A();

        virtual void virt_func() = 0;
};

class A_2 : public A {
    public:
        A_2();
        A_2(float value){ i = value ;};
        void virt_func() override {std::cout << i << std::endl ;};
};

class B {
    public:
        B();

        A* somefunc(float i){ A* a = new A_2(i); return a;};
};

class C {
    public:
        C();

        void somefunc(A* a){
            std::cout << a->i << std::endl ;
        };
};

PYBIND11_MODULE(my_module, m)
{
    m.doc() = "Some module.";

    py::class_<A> py_A(m, "A");
    py_A.def(py::init<>());

    py::class_<A_2> py_A_2(m, "A_2");
    py_A_2.def(py::init<>());
    py_A_2.def(py::init<float>(), py::arg("value"));
    py_A_2.def("virt_func", &A_2::virt_func);

    py::class_<B> py_B(m, "B");
    py_B.def(py::init<>());
    py_B.def("somefunc", &B::somefunc, py::arg("i"));
    
    py::class_<C> py_C(m, "B");
    py_C.def(py::init<>());
    py_C.def("somefunc", &C::somefunc, py::arg("a"));
};


Here are the corresponding compilation commands:


g++ \
    -O2 \
    -std=c++14 \
    $(python -m pybind11 --includes) \
    example.cpp \
    -lMDLSOLVER  \
    -ldl \
    -fPIC \
    -c \
    -nostartfiles \
    -o ./my_module.o

if test -f "my_module.o"
then
    g++ -std=c++14 -shared ./my_module.o -o my_module.so

    if test -f "my_module.so"
    then
        rm my_module.o

        python test_python.py
    fi
fi

and a python example of how we could use it from python:

import my_module

b = my_module.B()

a2 = b.somefunc(5.)

c = my_module.C()

c.somefunc(a2)

If i compile it, i get this error:

erreur: invalid new-expression of abstract class type ‘A’
     return new Class{std::forward<Args>(args)...};
                                                 ^
example.cpp:8:7: note:   because the following virtual functions are pure within ‘A’:
 class A {
       ^
example.cpp:13:22: note:        virtual void A::virt_func()
         virtual void virt_func() = 0;

If i bind A* instead of A, i get this error instead:

Traceback (most recent call last):
  File "test_python.py", line 1, in <module>
    import my_module
ImportError: /home/catA/tm267619/Workspace/MendelWork/BindingMendel/essai_bin_virtual/my_module.so: undefined symbol: _ZTV1A

Having A instead of A* as argument/return type of the functions somefunc are not fixing it in any way.

What can i do to be able to bind the classes B and C without error?

Thanks


Solution

  • The problem is solved using a Tranpoline class:

    #include <pybind11/pybind11.h>
    #include <pybind11/stl.h>
    #include <iostream>
    #include <sstream>
    
    namespace py = pybind11;
    
    class A {
        public:
            float i = 0;
    
            A() {};
    
            virtual void virt_func() = 0;
            void set_i(float val){i = val;};
    };
    
    class trampoline_A : public A{
        using A::A;
        
        void virt_func() override {
            PYBIND11_OVERRIDE_PURE(
                void, /* Return type */
                A,      /* Parent class */
                virt_func,          /* Name of function in C++ (must match Python name) */
                      /* Argument(s) */
            );
        };
    };
    class A_2 : public A {
        public:
            A_2() {};
            A_2(float value){ i = value ;};
            void virt_func() override {std::cout << "calling virt_func from A_2 : " << i << std::endl ;};
    };
    
    class B {
        public:
            B() {};
    
            A* somefunc(float i){ A* a = new A_2(i); return a;};
    };
    
    class C {
        public:
            C() {};
    
            void somefunc(A* a){
                std::cout << a->i << std::endl ;
            };
    };
    
    PYBIND11_MODULE(my_module, m)
    {
        m.doc() = "Some module.";
    
        py::class_<A, trampoline_A> py_A(m, "A") ;
        py_A.def(py::init<>());
        py_A.def("set_i", &A::set_i);
    
        py::class_<A_2, A> py_A_2(m, "A_2");
        py_A_2.def(py::init<>());
        py_A_2.def(py::init<float>());
    
        py_A_2.def("virt_func", &A_2::virt_func);
        py_A_2.def("set_i", &A_2::set_i);
        py_A_2.def("__repr__", [](A_2& a){
                                            std::stringstream ss;
                                            ss << a.i;
                                            std::string str = ss.str();
                                            std::string result = "A_2 object with value: " + str;
                                            return result;
                                        });
    
        py::class_<B> py_B(m, "B");
        py_B.def(py::init<>());
        py_B.def("somefunc", &B::somefunc, py::arg("i"));
        
        py::class_<C> py_C(m, "C");
        py_C.def(py::init<>());
        py_C.def("somefunc", &C::somefunc, py::arg("a"));
    };