Возможно ли в pybind11 использовать py::cast для доступа к абстрактному базовому классу?

Я включил минимальный рабочий пример ниже — его можно скомпилировать с использованием типичных инструкций pybind11 (я использую cmake).

У меня есть абстрактный базовый класс Abstract, который является чисто виртуальным. Я могу легко обернуть это в pybind11, используя «класс батута» (это хорошо задокументировано pybind11).

Кроме того, у меня есть конкретная реализация Abstract, ToBeWrapped, которая также упакована с помощью pybind11.

Моя проблема в том, что у меня есть некоторый клиентский код, который принимает произвольное PyObject* (или, в случае этого примера, оболочку pybind11 py::object) и ожидает, что это будет Abstract*.

Однако, как показано в моем примере, я не могу преобразовать py::object в Abstract*.

У меня нет проблем с преобразованием в ToBeWrapped*, а затем сохранением этого как Abstract*', however this would require my client code to know ahead of time what kind ofAbstract*`, которое отправляет интерпретатор Python, что противоречит цели абстрактного базового класса.

TL;DR

Можно ли изменить этот код так, чтобы клиент accessMethod мог произвольно обрабатывать Abstract*, переданный интерпретатором Python?

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

namespace py = pybind11;

// abstract base class - cannot be instantiated on its own
class Abstract
{
    public:
        virtual ~Abstract() = 0;
        virtual std::string print() const = 0;
};

Abstract::~Abstract(){}

// concrete implementation of Abstract
class ToBeWrapped : public Abstract
{
    public:
        ToBeWrapped(const std::string& msg = "heh?")
            : myMessage(msg){};
        std::string print() const override
        {
            return myMessage;
        }

    private:
        const std::string myMessage;
};

// We need a trampoline class in order to wrap this with pybind11
class AbstractPy : public Abstract
{
    public:
        using Abstract::Abstract;

        std::string print() const override
        {
            PYBIND11_OVERLOAD_PURE(
                std::string, // return type
                Abstract, // parent class
                print, // name of the function
                // arguments (if any)
            );
        }
};

// I have client code that accepts a raw PyObject* - this client code base implements its
// own python interpreter, and calls this "accessMethod" expecting to convert the python
// object to its c++ type.
//
// Rather than mocking up the raw PyObject* method (which would be trivial) I elected to
// keep this minimal example 100% pybind11
void accessMethod(py::object obj)
{
    // runtime error: py::cast_error
    //Abstract* casted = obj.cast<Abstract*>();

    // this works
    Abstract* casted = obj.cast<ToBeWrapped*>();
}

PYBIND11_MODULE(PyMod, m)
{
    m.doc() = R"pbdoc(
    This is a python module
    )pbdoc";

    py::class_<Abstract, AbstractPy>(m, "Abstract")
        .def("print", &Abstract::print)
    ;

    py::class_<ToBeWrapped>(m, "WrappedClass")
        .def(py::init<const std::string&>())
    ;
    m.def("access", &accessMethod, "This method will attempt to access the wrapped type");
}

person wesanyer    schedule 08.11.2019    source источник


Ответы (1)


Вам нужно объявить отношения иерархии, так что это:

py::class_<ToBeWrapped>(m, "WrappedClass")

должно быть:

py::class_<ToBeWrapped, Abstract>(m, "WrappedClass")
person Wim Lavrijsen    schedule 08.11.2019
comment
Хороший улов, я пропустил это в своем минимальном примере кода. Это означает, что мне не удалось эффективно воссоздать проблему, которая у меня есть в моей большей базе кода - я попытаюсь воссоздать ее и опубликовать как отдельный вопрос. - person wesanyer; 09.11.2019
comment
В большой кодовой базе попробуйте вариант: Abstract* casted = py::isinstance<ToBeWrapped>(obj) ? (Abstract*)obj.cast<void*>() : nullptr; - person Wim Lavrijsen; 09.11.2019
comment
Я попробую это, однако я проверил с помощью отладки и могу гарантировать, что я получаю экземпляр ToBeWrapped - на самом деле, сам pybind11 выдает мне ошибку времени выполнения, заявляя, что "Unable to cast Python instance of type <class 'ToBeWrapped'> to C++ type 'Abstract'. Однако я также пытался выполнить приведение напрямую к ToBeWrapped и все еще получаю сообщение об ошибке, поэтому я знаю, что происходит что-то подозрительное... - person wesanyer; 09.11.2019