Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

string to enum conversion regression after enum rewrite? #2114

Open
jskimko opened this issue Feb 14, 2020 · 0 comments
Open

string to enum conversion regression after enum rewrite? #2114

jskimko opened this issue Feb 14, 2020 · 0 comments

Comments

@jskimko
Copy link

jskimko commented Feb 14, 2020

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant