diff --git a/include/pybind11/pybind11.h b/include/pybind11/pybind11.h index 9cc45bdbdc..ccd7baee02 100644 --- a/include/pybind11/pybind11.h +++ b/include/pybind11/pybind11.h @@ -3171,7 +3171,6 @@ class enum_ : public class_ { public: using Base = class_; using Base::attr; - using Base::def; using Base::def_property_readonly; using Base::def_property_readonly_static; using Underlying = typename std::underlying_type::type; @@ -3275,6 +3274,28 @@ class enum_ : public class_ { pos_only()); } + template + enum_ &def(const char *name_, Func &&f, const Extra &...extra) { + if (std::strcmp(name_, "__str__") == 0) { + Base::def(name_, std::forward(f), prepend{}, extra...); + } else { + Base::def(name_, std::forward(f), extra...); + } + return *this; + } + + // Avoid using Base::def here: GCC 15/MinGW sees the duplicate dependent-base + // def(const char *, ...) template as ambiguous with enum_::def(const char *, ...). + template ::type, const char *>::value, + int> = 0> + enum_ &def(T &&op, const Extra &...extra) { + Base::def(std::forward(op), extra...); + return *this; + } + /// Export enumeration entries into the parent scope enum_ &export_values() { m_base.export_values(); diff --git a/tests/test_enum.cpp b/tests/test_enum.cpp index 4ec0af7245..2a7fa0fda0 100644 --- a/tests/test_enum.cpp +++ b/tests/test_enum.cpp @@ -24,6 +24,15 @@ TEST_SUBMODULE(enums, m) { .value("Two", ScopedEnum::Two) .value("Three", ScopedEnum::Three); + // test_enum_custom_str + enum class CustomStrEnum { A = 1, B = 2 }; + py::enum_(m, "CustomStrEnum") + .value("A", CustomStrEnum::A) + .value("B", CustomStrEnum::B) + .def("__str__", [](CustomStrEnum value) { + return "CustomStrEnum value " + std::to_string(static_cast(value)); + }); + m.def("test_scoped_enum", [](ScopedEnum z) { return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three"); }); diff --git a/tests/test_enum.py b/tests/test_enum.py index 52083caa03..969c30b1b0 100644 --- a/tests/test_enum.py +++ b/tests/test_enum.py @@ -277,6 +277,15 @@ def test_str_signature(): assert enum_type.__str__.__doc__.startswith("__str__") +def test_enum_custom_str_keeps_name_property(): + assert str(m.CustomStrEnum.A) == "CustomStrEnum value 1" + assert str(m.CustomStrEnum.B) == "CustomStrEnum value 2" + assert m.CustomStrEnum.A.name == "A" + assert m.CustomStrEnum.A.value == 1 + assert m.CustomStrEnum.B.name == "B" + assert m.CustomStrEnum.B.value == 2 + + def test_generated_dunder_methods_pos_only(): for enum_type in [m.ScopedEnum, m.UnscopedEnum]: for binary_op in [