Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 22 additions & 1 deletion include/pybind11/pybind11.h
Original file line number Diff line number Diff line change
Expand Up @@ -3171,7 +3171,6 @@ class enum_ : public class_<Type> {
public:
using Base = class_<Type>;
using Base::attr;
using Base::def;
using Base::def_property_readonly;
using Base::def_property_readonly_static;
using Underlying = typename std::underlying_type<Type>::type;
Expand Down Expand Up @@ -3275,6 +3274,28 @@ class enum_ : public class_<Type> {
pos_only());
}

template <typename Func, typename... Extra>
enum_ &def(const char *name_, Func &&f, const Extra &...extra) {
if (std::strcmp(name_, "__str__") == 0) {
Base::def(name_, std::forward<Func>(f), prepend{}, extra...);
} else {
Base::def(name_, std::forward<Func>(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 <typename T,
typename... Extra,
detail::enable_if_t<
!std::is_convertible<typename std::decay<T>::type, const char *>::value,
int> = 0>
enum_ &def(T &&op, const Extra &...extra) {
Base::def(std::forward<T>(op), extra...);
return *this;
}

/// Export enumeration entries into the parent scope
enum_ &export_values() {
m_base.export_values();
Expand Down
9 changes: 9 additions & 0 deletions tests/test_enum.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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_<CustomStrEnum>(m, "CustomStrEnum")
.value("A", CustomStrEnum::A)
.value("B", CustomStrEnum::B)
.def("__str__", [](CustomStrEnum value) {
return "CustomStrEnum value " + std::to_string(static_cast<int>(value));
});

m.def("test_scoped_enum", [](ScopedEnum z) {
return "ScopedEnum::" + std::string(z == ScopedEnum::Two ? "Two" : "Three");
});
Expand Down
9 changes: 9 additions & 0 deletions tests/test_enum.py
Original file line number Diff line number Diff line change
Expand Up @@ -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 [
Expand Down
Loading