pybind11 icon indicating copy to clipboard operation
pybind11 copied to clipboard

proposal: single template to check castability and return result as std::optional

Open eudoxos opened this issue 7 years ago • 1 comments

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?

eudoxos avatar Sep 26 '18 12:09 eudoxos


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

ljluestc avatar Nov 23 '24 20:11 ljluestc