Search code examples
pythonc++pybind11

Wrap python argument for pybind11 overloaded methods


I'm trying to create python bindings for some legacy C++ code which implemented its own 'extended' string class aString which it uses extensively. This works fine for .def(py::init<const std::string &, int>()), but once I start trying to wrap overloaded methods pybind11 does not appear to automatically cast std::string to aString. MWE adapted from the pybind11 docs:

src/example.cpp:

#include <pybind11/pybind11.h>

// Extended string class and struct in legacy library
class aString : public std::string
{
public:
    aString() : std::string() {}
    aString(const char *str) : std::string(str) {}
    aString(const std::string &str) : std::string(str) {}
};

struct Pet
{
    Pet(const aString &name, int age) : name(name), age(age) {}

    void set(int age_) { age = age_; }
    void set(const aString &name_) { name = name_; }

    aString name;
    int age;
};

// Python bindings
namespace py = pybind11;

PYBIND11_MODULE(example, m)
{
    py::class_<aString>(m, "aString")
        .def(py::init<const std::string &>())
        .def("__repr__",
             [](const aString &a)
             {
                 return "<aString ('" + a + "')>";
             });

    py::class_<Pet>(m, "Pet")
        .def(py::init<const std::string &, int>())
        .def("set", static_cast<void (Pet::*)(int)>(&Pet::set), "Set the pet's age")
        .def("set", static_cast<void (Pet::*)(const aString &)>(&Pet::set), "Set the pet's name")
        .def("__repr__",
             [](const Pet &a)
             {
                 return "<Pet ('" + a.name + "')>";
             });
    ;
}

CMakeLists.txt:

cmake_minimum_required(VERSION 3.4)
project(example)
add_subdirectory(pybind11)
pybind11_add_module(example src/example.cpp)

In python, the following works fine:

import example
p = example.Pet("Penny", 1)
p.set(3)
a = example.aString("Anne")
p.set(a)

However, the following fails with the message:

#> p.set("Jenny")
---------------------------------------------------------------------------
TypeError                                 Traceback (most recent call last)
<ipython-input-9-c5b0ff7c5315> in <module>
----> 1 p.set("Jenny")

TypeError: set(): incompatible function arguments. The following argument types are supported:
    1. (self: example.Pet, arg0: int) -> None
    2. (self: example.Pet, arg0: example.aString) -> None

Invoked with: <Pet ('Anne')>, 'Jenny'

I've tried changing my pybinding to be .def("set", static_cast<void (Pet::*)(const std::string &)>(&Pet::set), "Set the pet's name") instead of aString, but this will not compile as std::string is not one of the overload instances. I cannot add an additional overload instance for std:string in the C++ library.

I would like to be able to call p.set("Jenny") and let pybind11 convert "Jenny" into aString("Jenny"), rather than p.set(example.aString("Jenny")). This is my first project with pybind11 so any help would be appreciated, thanks!


Solution

  • You can enable an implicit conversion between types by adding the following statement:

    py::implicitly_convertible<std::string, aString>();
    

    In general py::implicitly_convertible<A, B>() works only if (as in your case) B has a constructor that takes A as its only argument.