pybind11
pybind11 copied to clipboard
proposal: single template to check castability and return result as std::optional
Issue description
I find myself often checking whether a particular py::object wraps this or that c++ class, and if it does, extract the result. This can be done with explicit try-catch block, like
py::object obj;
shared_ptr<T> i;
try { i=std::cast<shared_ptr<T>>(obj); } except(...) { };
if(i) // use i;
or yet worse, with the conversion done twice:
bool ok=true;
try { std::cast<shared_ptr<T>>(obj); } except(...) { ok=false };
if(ok){
std::cast<shared_ptr<T>> i=(obj);
/* .... */
}
What I propose is to have a template class encompassing this dual functionality (check whether the conversion is possible, return the result if it is). boost::python did something similar with extract::check() and extract::operator() but with c++17, it would be natural to use std::optional for this.
py::object obj;
py::optional_cast<T> i(obj);
if(i){ /* use i.value() */ };
or yet more compact
if(auto i=py::optional_cast<T>(obj)){ /* use i.value() */ }
Would this fit pybind11's design?
#include <pybind11/pybind11.h>
#include <pybind11/functional.h>
#include <pybind11/stl.h>
#include <optional>
#include <memory>
#include <iostream>
namespace py = pybind11;
template <typename T>
std::optional<std::shared_ptr<T>> optional_cast(const py::object& obj) {
try {
// Try casting the py::object to std::shared_ptr<T>
return std::make_shared<T>(obj.cast<std::shared_ptr<T>>());
} catch (const py::cast_error&) {
// If casting fails, return an empty optional
return std::nullopt;
}
}
class Pet {
public:
Pet() : name_("Pet") {}
virtual ~Pet() {}
virtual std::string name() const {
return name_;
}
private:
std::string name_;
};
class Dog : public Pet {
public:
Dog() : Pet() {}
std::string name() const override {
return "Dog";
}
};
PYBIND11_MODULE(example, m) {
py::class_<Pet, std::shared_ptr<Pet>>(m, "Pet")
.def(py::init<>())
.def("name", &Pet::name);
py::class_<Dog, Pet, std::shared_ptr<Dog>>(m, "Dog")
.def(py::init<>())
.def("name", &Dog::name);
m.def("optional_cast_test", [](py::object obj) {
// Use the new optional_cast function
auto pet = optional_cast<Pet>(obj);
if (pet) {
std::cout << "Successfully cast to Pet: " << pet.value()->name() << std::endl;
} else {
std::cout << "Failed to cast to Pet." << std::endl;
}
auto dog = optional_cast<Dog>(obj);
if (dog) {
std::cout << "Successfully cast to Dog: " << dog.value()->name() << std::endl;
} else {
std::cout << "Failed to cast to Dog." << std::endl;
}
});
}