If you wanted to make SWIG read boost/preprocessor.hpp you'd do that with:
%module minimal
%{
#include "minimal.h"
%}
%include <boost/preprocessor.hpp>
%include "minimal.h"
Since by default SWIG doesn't follow #include
directives. (You could also use -includeall
to make it follow them instead). In this case though I think making the SWIG preprocessor make any kind of sense of the crazy magic that the Boost preprocessor library uses is a lost cause.
Instead though we can try to get something with equally nice, but "Pythonic" syntax instead. In esscence what we're going to do is write a totally different version of DEFINE_ENUM_WITH_STRING_CONVERSIONS
for SWIG wrappers only. It will be compatible with the definitions seen by C++ though.
To do this I'm going to start by splitting your file minimal.h into two files. One with the macro definition and one that uses it. (We could have done this different ways, for example by wrapping the macro definitions with #ifndef DEFINE_ENUM_WITH_STRING_CONVERSIONS
or #ifndef SWIG
, which would be equally valid solutions).
Thus we now have enum.hh:
#ifndef ENUM_H
#define ENUM_H
#include <boost/preprocessor.hpp>
//Found this here: https://stackoverflow.com/questions/5093460/how-to-convert-an-enum-type-variable-to-a-string
#define X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE(r, data, elem) \
case elem : return BOOST_PP_STRINGIZE(elem);
#define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name, enumerators) \
enum name { \
BOOST_PP_SEQ_ENUM(enumerators) \
}; \
\
inline const char* ToString(name v) \
{ \
switch (v) \
{ \
BOOST_PP_SEQ_FOR_EACH( \
X_DEFINE_ENUM_WITH_STRING_CONVERSIONS_TOSTRING_CASE, \
name, \
enumerators \
) \
default: return "[Unknown " BOOST_PP_STRINGIZE(name) "]"; \
} \
}
#endif
And minimal.h:
#ifndef MINIMAL_H
#define MINIMAL_H
#include "enum.h"
DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif
So your minimal.cpp continues to work as before, but now we can write a SWIG module that at least compiles, even if it doesn't do anything useful yet:
%module minimal
%{
#include "minimal.h"
%}
%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%enddef
%include "minimal.h"
This currently has a stub, SWIG specific macro that we're going to fill out. It's a little ugly how I've done this, simply because I'm trying to avoid changing the way the existing macro is defined/used at all.
What I produced as a starting point is another file, enum.i:
%include <std_vector.i>
%include <std_string.i>
%{
#include <vector>
#include <string>
#include <tuple>
%}
%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
typedef std::tuple<name,std::string> name ## _entry;
struct name ## _helper {
std::vector<name ## _entry> list;
name ## _helper(const name value) {
list.push_back(std::make_tuple(value,ToString(value)));
}
name ## _helper operator()(const name value) {
list.push_back(std::make_tuple(value,ToString(value)));
return *this;
}
};
static const std::vector<name ## _entry> name ## _list = name ## _helper enumerators . list;
%}
struct name ## _entry {
%extend {
const unsigned long value {
return std::get<0>(*$self);
}
const std::string& label {
return std::get<1>(*$self);
}
}
};
%template(name ## vec) std::vector<name ## _entry>;
const std::vector<name ## _entry> name ## _list;
%enddef
Such that minimal.i just needs to become:
%module minimal
%{
#include "minimal.h"
%}
%include "enum.i"
%include "minimal.h"
All that macro does is take the value of enumerators
, which is going to be something like (A)(B)
and generate some code that's completely standard (if quirky) C++ that expands this into a std::vector<std::tuple<my_enum,std::string>>
. That's done by mapping the first enum member onto a constructor call, and the rest onto an overloaded operator()
. We use the ToString()
supplied by enum.h to find the string representation. Finally our macro has enough information to wrap the vector of tuples in a way which makes sense from within Python.
With this in place we can do something like:
import minimal
print ", ".join(("%s(%d)" % (x.label,x.value) for x in minimal.my_enum_list))
Which, when compiled and run gives:
A(0), B(1)
I.e. enough to start writing Python code that's aware of both the label and the value of a C++ enum.
But let's not stop there! Why did I deliberately call the resulting vector my_enum_list
instead of just my_enum
? Because there's more we can do now.
Python 2.7 doesn't have any default "enum-ish", but that doesn't prevent us from wrapping this as something both Pythonic and natural to people who know about enums. I made my Python 2.7 enum support by reading this other answer. To start with I added some generic enum support routines to the file using %pythoncode
, (labelled #1 in final source) but outside the SWIG macro since there's no need to vary it. I also added a %pythoncode
inside the SWIG macro (labelled #2) that invokes this once per actual enum. In order to make this work I had to convert the const std::vector
from the previous version into a function so that it was accessible in the right part of the generated Python. Finally I had to show SWIG a forward declaration of the real enum, in order to persuade it to actually accept that as an argument to functions. The final result is:
%include <std_vector.i>
%include <std_string.i>
%{
#include <vector>
#include <string>
#include <tuple>
%}
// #1
%pythoncode %{
class EnumValue(int):
def __new__(cls,v,l):
result = super(EnumValue,cls).__new__(cls,v)
result._value = l
return result
def __str__(self):
return self._value
def make_enum(name,enums):
return type(name, (), enums)
%}
%define DEFINE_ENUM_WITH_STRING_CONVERSIONS(name,enumerators)
%{
typedef std::tuple<name,std::string> name ## _entry;
struct name ## _helper {
std::vector<name ## _entry> list;
name ## _helper(const name value) {
list.push_back(std::make_tuple(value,ToString(value)));
}
name ## _helper operator()(const name value) {
list.push_back(std::make_tuple(value,ToString(value)));
return *this;
}
};
static const std::vector<name ## _entry> name ## _list() {
return name ## _helper enumerators . list;
}
%}
struct name ## _entry {
%extend {
const unsigned long value {
return std::get<0>(*$self);
}
const std::string& label {
return std::get<1>(*$self);
}
}
};
%template(name ## vec) std::vector<name ## _entry>;
const std::vector<name ## _entry> name ## _list();
// #2
%pythoncode %{
name = make_enum('name', {x.label: EnumValue(x.value, x.label) for x in name ## _list()})
%}
enum name;
%enddef
I added a function to minimal.i to prove it really does work:
%module minimal
%{
#include "minimal.h"
%}
%include "enum.i"
%include "minimal.h"
%inline %{
void foo(const my_enum& v) {
std::cerr << "GOT: " << v << "\n";
}
%}
And finally test it with:
import minimal
print minimal.my_enum
print minimal.my_enum.A
print minimal.my_enum.B
minimal.foo(minimal.my_enum.B)
Which you'll be pleased to see worked and resulted in:
<class 'minimal.my_enum'>
A
B
GOT: 1
If you're using Python 3 there's a possibly nicer way to represent enums, but I'll leave that as an exercise for the reader for now. You can obviously tweak the Python 2.7 fake enums to your taste as well.