Pybind11-强制转换抽象继承的类元素的列表

问题描述

我有一个纯抽象类和2个继承类,其中每个类都包含一个称为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一个抽象类,因此编译器返回:

错误:无效的抽象返回类型“动物”

但是,如果列表是在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,); }
};

// 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,); }
};

// 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,PyDog>(m,"Dog")
        .def(py::init<>());

    py::class_<Cat,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社区提供的解决方案