4

I'm utilizing the enum with ToString implementation that was suggested here: How to convert an enum type variable to a string? It utilizes and works fine as far as I can tell.

My issues arise when I try to wrap and export the macro to a Python library wrapped with SWIG. Similar question: SWIG errors because of preprocessor directive There, the solution was to add headers / declarations to the SWIG interface. I haven't had success with this so far. Chances are that I just don't know what I have to add.

Tried:

%include <boost/preprocessor/config/config.hpp>
%include <boost/preprocessor/stringize.hpp>
%include <boost/preprocessor/seq/for_each.hpp>
%include <boost/preprocessor/seq/enum.hpp>

MWE:

minimal.h

#ifndef MINIMAL_H
#define MINIMAL_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) "]";         \
        }                                                                     \
    }

DEFINE_ENUM_WITH_STRING_CONVERSIONS(my_enum, (A)(B))
#endif

minimal.cpp

#include <iostream> 
#include "minimal.h"

int main(){
    using namespace std;
    cout << A << ": " << ToString(A) << endl;
    cout << B << ": " << ToString(B) << endl;

}

minimal.i

%module minimal
%{
#include "minimal.h"
%}
%include "minimal.h"

The error is not very indicative. Line 29 is the actual definition of my_enum.

matthias@rp3deb:~/dvl/swig_boost_minimal$ swig minimal.i
minimal.h:29: Error: Syntax error in input(1).

Any advice on how I could wrap this?

Community
  • 1
  • 1
Matthias Kauer
  • 9,697
  • 5
  • 17
  • 19
  • I almost have an answer, but out of curiosity are you targeting python 2.x or 3.x? – Flexo Aug 08 '14 at 20:53
  • I'm still on Python 2.7. The last time I considered switching (a while ago), Python 3 was still missing some libraries I require. – Matthias Kauer Aug 13 '14 at 02:41

1 Answers1

3

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.

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272
  • This is a tremendous answer and I'm sure it works. Unfortunately, it somewhat misses the question. It creates a lot of duplicate code and does not solve the issue of including boost headers. I guess I was hoping there would be a way to nicely include the right boost headers. There is so many of them however, so it must be really tedious. Given that you really know your way around SWIG and chose to go this route confirms that, I would say. I'm accepting this answer as an "it's probably not worth bothering with boost macros that need to be externalized" – Matthias Kauer Aug 13 '14 at 02:50