J'ai une classe abstraite pure et 2 classes héritées où chacune contient une méthode appelée go comme suit :

// 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! "; }
};

Côté Python, une liste d'objets instanciés (soit Chien ou Chat) est définie. Côté C++, j'essaye d'écrire une fonction call_go, qui prend cette liste Python en entrée, et appelle la méthode go de chaque objet de la liste.

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)

Comme je ne connais pas à l'avance le type de chaque élément de la liste donnée, j'ai essayé d'écrire la fonction call_go où j'ai converti l'élément en Animal :

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

Mais, comme Animal est une classe abstraite, le compilateur renvoie :

erreur : type de retour abstrait invalide « Animal »

Cependant, si la liste est entièrement définie dans le code c++, elle se compile et s'exécute parfaitement :

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;
}

Savez-vous comment résoudre ce problème ? Je suppose que cela implique d'écrire un casting personnalisé.

Le code C++ complet est ici :

#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);
}
0
m18855 8 nov. 2020 à 23:02

1 réponse

Meilleure réponse

La bonne façon d'écrire call_go est

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() << " ";
  }
}

Merci à la communauté pybind11 sur https://gitter.im/pybind/Lobby pour la solution

0
m18855 9 nov. 2020 à 11:14