From 613541947a0a2be1680785f95d04b68bcbc33c85 Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Wed, 3 Aug 2016 23:45:08 -0400 Subject: [PATCH 1/2] Fix scoped enums and add scoped enum example PR #309 broke scoped enums, which failed to compile because the added: value == value2 comparison isn't valid for a scoped enum (they aren't implicitly convertible to the underlying type). This commit fixes it by explicitly converting the enum value to its underlying type before doing the comparison. It also adds a scoped enum example to the constants-and-functions example that triggers the problem fixed in this commit. --- example/example-constants-and-functions.cpp | 15 +++++++++++++++ example/example-constants-and-functions.py | 3 +++ example/example-constants-and-functions.ref | 2 ++ include/pybind11/pybind11.h | 4 ++-- 4 files changed, 22 insertions(+), 2 deletions(-) diff --git a/example/example-constants-and-functions.cpp b/example/example-constants-and-functions.cpp index b71856cbe..d7f8ad174 100644 --- a/example/example-constants-and-functions.cpp +++ b/example/example-constants-and-functions.cpp @@ -14,6 +14,11 @@ enum EMyEnumeration { ESecondEntry }; +enum class ECMyEnum { + Two = 2, + Three +}; + class ExampleWithEnum { public: enum EMode { @@ -41,6 +46,10 @@ float test_function3(int i) { return (float) i / 2.f; } +void test_ecenum(ECMyEnum z) { + std::cout << "test_ecenum(ECMyEnum::" << (z == ECMyEnum::Two ? "Two" : "Three") << ")" << std::endl; +} + py::bytes return_bytes() { const char *data = "\x01\x00\x02\x00"; return std::string(data, 4); @@ -56,6 +65,7 @@ void init_ex_constants_and_functions(py::module &m) { m.def("test_function", &test_function1); m.def("test_function", &test_function2); m.def("test_function", &test_function3); + m.def("test_ecenum", &test_ecenum); m.attr("some_constant") = py::int_(14); py::enum_(m, "EMyEnumeration") @@ -63,6 +73,11 @@ void init_ex_constants_and_functions(py::module &m) { .value("ESecondEntry", ESecondEntry) .export_values(); + py::enum_(m, "ECMyEnum") + .value("Two", ECMyEnum::Two) + .value("Three", ECMyEnum::Three) + ; + py::class_ exenum_class(m, "ExampleWithEnum"); exenum_class.def_static("test_function", &ExampleWithEnum::test_function); py::enum_(exenum_class, "EMode") diff --git a/example/example-constants-and-functions.py b/example/example-constants-and-functions.py index f9292ee9e..0a5224a02 100755 --- a/example/example-constants-and-functions.py +++ b/example/example-constants-and-functions.py @@ -6,6 +6,7 @@ sys.path.append('.') from example import test_function from example import some_constant from example import EMyEnumeration +from example import ECMyEnum, test_ecenum from example import EFirstEntry from example import ExampleWithEnum from example import return_bytes @@ -20,6 +21,8 @@ print(test_function()) print(test_function(7)) print(test_function(EMyEnumeration.EFirstEntry)) print(test_function(EMyEnumeration.ESecondEntry)) +test_ecenum(ECMyEnum.Three) +test_ecenum(ECMyEnum.Two) print("enum->integer = %i" % int(EMyEnumeration.ESecondEntry)) print("integer->enum = %s" % str(EMyEnumeration(2))) diff --git a/example/example-constants-and-functions.ref b/example/example-constants-and-functions.ref index 1d08223f8..f82480694 100644 --- a/example/example-constants-and-functions.ref +++ b/example/example-constants-and-functions.ref @@ -10,6 +10,8 @@ test_function(enum=1) None test_function(enum=2) None +test_ecenum(ECMyEnum::Three) +test_ecenum(ECMyEnum::Two) enum->integer = 2 integer->enum = EMyEnumeration.ESecondEntry A constant = 14 diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index ae5a75420..6bab4a95e 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1019,9 +1019,9 @@ public: this->def("__init__", [](Type& value, UnderlyingType i) { new (&value) Type((Type) i); }); this->def("__int__", [](Type value) { return (UnderlyingType) value; }); this->def("__eq__", [](const Type &value, Type *value2) { return value2 && value == *value2; }); - this->def("__eq__", [](const Type &value, UnderlyingType value2) { return value == value2; }); + this->def("__eq__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value == value2; }); this->def("__ne__", [](const Type &value, Type *value2) { return !value2 || value != *value2; }); - this->def("__ne__", [](const Type &value, UnderlyingType value2) { return value != value2; }); + this->def("__ne__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value != value2; }); this->def("__hash__", [](const Type &value) { return (UnderlyingType) value; }); m_entries = entries; } From d41a273031770112fdec6fb03e8259c4b103e23a Mon Sep 17 00:00:00 2001 From: Jason Rhinelander Date: Thu, 4 Aug 2016 00:21:37 -0400 Subject: [PATCH 2/2] Only support ==/!= int on unscoped enums This makes the Python interface mirror the C++ interface: pybind11-exported scoped enums aren't directly comparable to the underlying integer values. --- example/example-constants-and-functions.py | 21 ++++++++++++++++++++- example/example-constants-and-functions.ref | 2 ++ include/pybind11/pybind11.h | 9 +++++++-- 3 files changed, 29 insertions(+), 3 deletions(-) diff --git a/example/example-constants-and-functions.py b/example/example-constants-and-functions.py index 0a5224a02..852902f07 100755 --- a/example/example-constants-and-functions.py +++ b/example/example-constants-and-functions.py @@ -22,7 +22,26 @@ print(test_function(7)) print(test_function(EMyEnumeration.EFirstEntry)) print(test_function(EMyEnumeration.ESecondEntry)) test_ecenum(ECMyEnum.Three) -test_ecenum(ECMyEnum.Two) +z = ECMyEnum.Two +test_ecenum(z) +try: + z == 2 + print("Bad: expected a TypeError exception") +except TypeError: + try: + z != 3 + print("Bad: expected a TypeError exception") + except TypeError: + print("Good: caught expected TypeError exceptions for scoped enum ==/!= int comparisons") + +y = EMyEnumeration.ESecondEntry +try: + y == 2 + y != 2 + print("Good: no TypeError exception for unscoped enum ==/!= int comparisions") +except TypeError: + print("Bad: caught TypeError exception for unscoped enum ==/!= int comparisons") + print("enum->integer = %i" % int(EMyEnumeration.ESecondEntry)) print("integer->enum = %s" % str(EMyEnumeration(2))) diff --git a/example/example-constants-and-functions.ref b/example/example-constants-and-functions.ref index f82480694..480563114 100644 --- a/example/example-constants-and-functions.ref +++ b/example/example-constants-and-functions.ref @@ -12,6 +12,8 @@ test_function(enum=2) None test_ecenum(ECMyEnum::Three) test_ecenum(ECMyEnum::Two) +Good: caught expected TypeError exceptions for scoped enum ==/!= int comparisons +Good: no TypeError exception for unscoped enum ==/!= int comparisions enum->integer = 2 integer->enum = EMyEnumeration.ESecondEntry A constant = 14 diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 6bab4a95e..bfb0f07ad 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -1019,9 +1019,14 @@ public: this->def("__init__", [](Type& value, UnderlyingType i) { new (&value) Type((Type) i); }); this->def("__int__", [](Type value) { return (UnderlyingType) value; }); this->def("__eq__", [](const Type &value, Type *value2) { return value2 && value == *value2; }); - this->def("__eq__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value == value2; }); this->def("__ne__", [](const Type &value, Type *value2) { return !value2 || value != *value2; }); - this->def("__ne__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value != value2; }); + if (std::is_convertible::value) { + // Don't provide comparison with the underlying type if the enum isn't convertible, + // i.e. if Type is a scoped enum, mirroring the C++ behaviour. (NB: we explicitly + // convert Type to UnderlyingType below anyway because this needs to compile). + this->def("__eq__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value == value2; }); + this->def("__ne__", [](const Type &value, UnderlyingType value2) { return (UnderlyingType) value != value2; }); + } this->def("__hash__", [](const Type &value) { return (UnderlyingType) value; }); m_entries = entries; }