Skip to content

string to enum conversion regression after enum rewrite? #2114

@jskimko

Description

@jskimko

Issue description

I have a string-enum converter that works using v2.2.4. This breaks starting from v2.3.0 due to the enum_ rewrite.

In v2.2.4, it was possible to support this conversion via implicitly_convertible. It looks like other people are taking advantage of this feature as well: #483 and #1122.

From v2.3.0+, it appears that this codepath is not being covered by the new PYBIND11_ENUM_OP_CONV and PYBIND11_ENUM_OP_CONV_LHS implementations. These macros appear to only support integer conversion and fails to invoke the existing pathways enabled through implicitly_convertible.

I am wondering if this is a regression or if only integer conversions will be allowed going forward?

Tested using v2.2.4, v2.3.0, v.2.4.3.

Reproducible example code

// example.cc
#include <pybind11/pybind11.h>                                                  
namespace py = pybind11;                                                        
                                                                                
template <typename T>                                                           
T pyStringToEnum(const py::enum_<T>& enm, const std::string& value) {           
    auto values = enm.attr("__members__").template cast<py::dict>();            
    auto strVal = py::str(value);                                               
    if (values.contains(strVal)) {                                              
        return T(values[strVal].template cast<T>());                            
    }                                                                           
    throw "Invalid string value " + value + " for enum " + std::string(typeid(T).name());
}                                                                               
                                                                                
template <typename T>                                                           
py::str enumToPyString(const py::enum_<T>& enm, const T& value) {               
    auto values = enm.attr("__members__").template cast<py::dict>();            
    for (auto val : values) {                                                   
        if (T(val.second.template cast<T>()) == value) {                        
            return py::str(val.first);                                          
        }                                                                       
    }                                                                           
    throw "Invalid value for enum " + std::string(typeid(T).name());            
}                                                                               
                                                                                
enum class Kind { X = 0, Y = 1, Z = 2 };                                        
struct A {
    A() : kind(Kind::X) {}                                                                      
    Kind kind;                                                                  
};                                                                              
                                                                                
PYBIND11_MODULE(example, m) {                                                   
    py::enum_<Kind> enm(m, "Kind");                                             
    enm                                                                         
        .value("X", Kind::X)                                                    
        .value("Y", Kind::Y)                                                    
        .value("Z", Kind::Z)                                                    
        .def(py::init([enm](const std::string& value) -> Kind {                 
            return pyStringToEnum(enm, py::str(value));                         
        }))                                                                     
        .def("__str__", [enm](Kind e) { return enumToPyString(enm, e); });      
                                                                                
    py::implicitly_convertible<std::string, Kind>();                            
    py::implicitly_convertible<int, Kind>();                                    
                                                                                
    py::class_<A>(m, "A")                                                       
        .def(py::init<>())                                                      
        .def_readwrite("kind", &A::kind);                                       
} 
$ g++ -std=c++11 -fPIC -shared $(python-config --includes) $(python-config --libs) -Ipybind11-${VERSION}/include/ example.cc -o example.so 
# test.py
from example import A                                                              
                                                                                   
a = A()                                                                            
kind = a.kind                                                                      
                                                                                   
s = 'kind == "X"'                                                                  
print '{:15} ->   expected: {:<5} result: {:<5}'.format(s, True, eval(s))          
                                                                                   
s = 'kind == None'                                                                 
print '{:15} ->   expected: {:<5} result: {:<5}'.format(s, False, eval(s))         
                                                                                   
s = 'kind == 0'                                                                    
print '{:15} ->   expected: {:<5} result: {:<5}'.format(s, True, eval(s))          
                                                                                   
s = 'kind == 1'                                                                    
print '{:15} ->   expected: {:<5} result: {:<5}'.format(s, False, eval(s))
# output v2.3.0+
kind == "X"     ->   expected: 1     result: 0    
kind == None    ->   expected: 0     result: 0    
kind == 0       ->   expected: 1     result: 0    
kind == 1       ->   expected: 0     result: 0 
# output v2.2.4
kind == "X"     ->   expected: 1     result: 1    
kind == None    ->   expected: 0     result: 0    
kind == 0       ->   expected: 1     result: 1    
kind == 1       ->   expected: 0     result: 0

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