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
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions