Skip to content

Implicit conversion from Python string to C++ enum type #483

@stukowski

Description

@stukowski

Let me propose an extension:
Sometimes it would be nice if a string could be passed from a Python script to a C++ function where an enum parameter is expected. For example:

struct Pet {
    enum Kind {
        Dog = 0,
        Cat
    };
    Pet(Kind type) : type(type) { }
    Kind type;
};

Binding code:

auto pet = py::class_<Pet>(m, "Pet")
    .def(py::init<Pet::Kind>());

py::enum_<Pet::Kind>(pet, "Kind")
    .value("Dog", Pet::Kind::Dog)
    .value("Cat", Pet::Kind::Cat);

Python:

>>> p = Pet(Pet.Cat) # Works
>>> p = Pet("Cat")   # Would like to make this work too

In principle it is possible to add optional support for this implicit conversion from a Python string to the enum type. I propose to add the following method to the pybind11::enum_ class:

template <typename Type> class enum_ : public class_<Type> {
    ...

    /// Set up atomatic Python string -> C++ enum conversion.
    enum_& enable_conversion_from_string() {
        auto entries = m_entries;
	def("__init__", [entries](Type& v, const char* str) {
            for(const auto& item : *entries) {
                if(!strcmp(str, item.second)) {
                    v = (Type)item.first;
                    return;
                }
            }
            std::string tname = typeid(Type).name();
            detail::clean_type_id(tname);
            throw value_error("\"" + std::string(str) + "\" is not a valid value for enum type " + tname);
        });
	implicitly_convertible<str, Type>();
        return *this;
    }
    ...
}

This would allow writing binding codes like this:

py::enum_<Pet::Kind>(pet, "Kind")
    .value("Dog", Pet::Kind::Dog)
    .value("Cat", Pet::Kind::Cat)
    .enable_conversion_from_string();

There is only one problem with this solution: The exception thrown by the conversion constructor when the string does not match any of the enum entries is swallowed by pybind11. Instead, a general cast error is raised because the conversion from string to the enum type failed.

I am wondering what you think about this idea. Does it make sense to offer an automatic conversion like this? At least in my project there is one situation where an automatic conversion from a string is desirable.

By the way: The pybind11::enum_ class constructor registers two __init__ methods: One initializing the value via assignment, the other via in-place new. I think one of them is redundant. Furthermore, I noticed that the std::unordered_map allocated by the constructor is never freed. Does it make sense to use a shared_ptr<unordered_map> here so that the map gets destroyed eventually?

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions