Pybind11 - 转换抽象继承 class 元素的列表

Pybind11 - Casting a list of abstract-inherited class elements

我有一个纯抽象 class 和 2 个继承的 classes,其中每个包含一个名为 go 的方法,如下所示:

// Pure abstract class
class Animal {
public:
    virtual std::string go() = 0;
};

// First inherited class
class Dog : public Animal {
public:
    std::string go() override { return "woof! "; }
};

// Second inherited class
class Cat : public Animal {
public:
    std::string go() override { return "meow! "; }
};

在 Python 端,定义了实例化对象列表(Dog 或 Cat)。在 C++ 方面,我正在尝试编写一个函数 call_go,它将此 Python 列表作为输入,并调用列表中每个对象的方法 go

import test

# defining a list of objects Cat or Dog
animals = []
animals.append(test.Cat())
animals.append(test.Dog())
animals.append(test.Cat())

# trying to call for each object of the given list the method "go"
test.call_go(animals)

因为我事先不知道给定列表中每个元素的类型,所以我尝试编写函数 call_go 并将元素转换为 Animal:

void call_go(py::list animals) {
  for (py::handle animal : animals) {
    std::cout << py::cast<Animal>(animal).go() << " ";
  }
}

但是,由于 Animal 是一个抽象 class,编译器 returns:

error: invalid abstract return type ‘Animal’

但是,如果该列表在 C++ 代码中完全定义,它可以完美地编译和运行:

void call_go_cpp() {
  std::list<Animal*> animals;

  animals.push_back(new Cat());
  animals.push_back(new Dog());
  animals.push_back(new Cat());

  for(Animal* animal: animals)
    std::cout << animal->go() << std::endl;
}

你知道如何解决这个问题吗?我想这涉及编写自定义演员表。

完整的 C++ 代码在这里:

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

namespace py = pybind11;

// Pure abstract class
class Animal {
public:
    virtual std::string go() = 0;
};

class PyAnimal : public Animal {
public:
    using Animal::Animal;
    std::string go() override { PYBIND11_OVERRIDE_PURE( std::string, Animal, go, ); }
};

// First inherited class
class Dog : public Animal {
public:
    std::string go() override { return "woof! "; }
};

class PyDog : public Dog {
public:
    using Dog::Dog;
    std::string go() override { PYBIND11_OVERRIDE(std::string, Dog, go, ); }
};

// Second inherited class
class Cat : public Animal {
public:
    std::string go() override { return "meow! "; }
};

class PyCat : public Cat {
public:
    using Cat::Cat;
    std::string go() override { PYBIND11_OVERRIDE(std::string, Cat, go, ); }
};

// calling the method "go" for each element of a list (instance of class Dog or Cat) created within the c++ code 
void call_go_cpp() {
  std::list<Animal*> animals;

  animals.push_back(new Cat());
  animals.push_back(new Dog());
  animals.push_back(new Cat());

  for(Animal* animal: animals)
    std::cout << animal->go() << std::endl;
}

// trying to call the method "go" for each element of a list (instance of class Dog or Cat) defined on the Python side
void call_go(py::list animals) {
  for (py::handle animal : animals) {
    std::cout << py::cast<Animal>(animal).go() << " ";
  }
}

// Pybind11 bindings
PYBIND11_MODULE(test, m) {
    py::class_<Animal, PyAnimal>(m, "Animal")
        .def(py::init<>())
        .def("go", &Animal::go);

    py::class_<Dog, Animal, PyDog>(m, "Dog")
        .def(py::init<>());

    py::class_<Cat, Animal, PyCat>(m, "Cat")
        .def(py::init<>());

    m.def("call_go_cpp", &call_go_cpp);
    m.def("call_go", &call_go);
}

call_go的正确写法是

void call_go(py::list animals) {
  for (py::handle animal : animals) {
    // wrong 
    //std::cout << py::cast<Animal>(animal).go() << " ";
    // correct way
    std::cout << py::cast<Animal *>(animal)->go() << " ";
  }
}

感谢 https://gitter.im/pybind/Lobby 的 pybind11 社区提供的解决方案