15

I am trying to wrap an enum class in a c++ header file for use in a cython project.

For example, how can this

enum class Color {red, green = 20, blue};

be wrapped with Cython.

ead
  • 32,758
  • 6
  • 90
  • 153
user3684792
  • 2,542
  • 2
  • 18
  • 23
  • 1
    Can you elaborate with an example having `cdef enum Foo: [...]` does not answer your needs ? – coincoin Jun 23 '15 at 11:54
  • 1
    I am not wrapping a c enum, but a c++ enum class so this doesn't work – user3684792 Jun 23 '15 at 11:57
  • Do the enum as a `ctypedef` and the contents of the enum in a namespace? (I haven't tested this, but it seems it might work) – DavidW Jun 24 '15 at 06:24
  • David can you elaborate please? – user3684792 Jun 24 '15 at 13:17
  • I'm thinking `ctypedef int enum_type` or possible `cdef cppclass enum_type: pass` to define the type. Then to define the members doing `cdef extern from "somefile.hpp" namespace "enum_type": cdef enum_type BLUE` (etc). I think something like that should generate the right c++ code (i.e. `BLUE` will be replaced with `enum_type::BLUE`) – DavidW Jun 24 '15 at 13:45
  • This is an interesting idea. Will try tomorrow when I get to my machine – user3684792 Jun 24 '15 at 20:42
  • Did you ever get this working? – AdmiralJonB Oct 26 '15 at 00:48
  • Yes, but I hacked it. The use case I wanted was to access a member function of a class that returned an enum class. I had to change this member function in my target library to return an 'enum class wrap'. This was basically a new class that had an enum class as its only member and appropriate accessors to get the value. Ugly I know... – user3684792 Oct 27 '15 at 14:08

4 Answers4

14

CPP class

enum class Color {red, green = 20, blue}; 

Definition of type

cdef extern from "colors.h":
  cdef cppclass Color:
    pass

Definition of color types

cdef extern from "colors.h" namespace "Color":
  cdef Color red
  cdef Color green
  cdef Color blue

Python implementation

cdef class PyColor:
  cdef Color thisobj
  def __cinit__(self, int val):
    self.thisobj = <Color> val

  def get_color_type(self):
    cdef c = {<int>red : "red", <int> green : "green", <int> blue : "blue"}
    return c[<int>self.thisobj]
user985030
  • 1,557
  • 1
  • 16
  • 32
  • 2
    With Cython version >= 3.0 consider this answer: https://stackoverflow.com/a/67138945/5769463 – ead Apr 18 '21 at 08:09
10

The latest cython (3.x) has a direct support for c++ enum class, it's documented here: https://cython.readthedocs.io/en/latest/src/userguide/wrapping_CPlusPlus.html#scoped-enumerations

Here is an example:

// cpp header
enum class State: int
{
    Good,
    Bad,
    Unknown,
};


const char* foo(State s){
    switch (s){
        case State::Good:
            return "Good";
        case State::Bad:
            return "Bad";
        case State::Unknown:
            return "Unknown";
    }
}

Cython's side

cdef extern from "test.h":
    cpdef enum class State(int):
        Good,
        Bad,
        Unknown,

    const char* foo(State s)
    
def py_foo(State s):
    return foo(s)

call py_foo(State.Good) returns b'Good'

oz1
  • 938
  • 7
  • 18
  • Pretty cool. I asked this question 6 years ago, and it is nice to finally be able to accept a real native answer, though thank you to all of the other answers that provided decent workarounds – user3684792 Apr 20 '21 at 10:41
5

Another alternative that allows using PEP-435 Enums as mentioned in Cython docs is as follows:

foo.h

namespace foo {
enum class Bar : uint32_t {
    Zero = 0,
    One = 1
};
}

foo.pxd

from libc.stdint cimport uint32_t

cdef extern from "foo.h" namespace 'foo':

    cdef enum _Bar 'foo::Bar':
        _Zero 'foo::Bar::Zero'
        _One  'foo::Bar::One'


cpdef enum Bar:
    Zero = <uint32_t> _Zero
    One  = <uint32_t> _One

main.pyx

from foo cimport Bar

print(Bar.Zero)
print(Bar.One)

# or iterate over elements
for value in Bar:
    print(value)
jadarve
  • 309
  • 3
  • 7
4

Here's an alternative solution that uses the ability to change the name of cython and C++ identifiers.

header.hpp

namespace foo {
enum class Bar : uint32_t {
    BAZ,
    QUUX
};
}

header.pxd

cdef extern from "header.hpp" namespace "foo::Bar":
    cdef enum Bar "foo::Bar":
        BAZ,
        QUUX

main.pyx

from header cimport *
cdef void doit(Bar b):
    pass

doit(BAZ) # Not Bar.BAZ, which would have been nicer.

It's effectively telling cython that there exists a namespace called "foo::Bar", and puts a C-style enum in it. To counteract the fact that Bar would otherwise become "foo::Bar::Bar" that is also given an overridden name. It does mean that Bar::BAZ is referred to as BAZ in cython, rather than Bar.BAZ which would be a more idiomatic representation of enum classes, but it seems close enough.

Rich L
  • 1,905
  • 2
  • 19
  • 30