1

I've been using the REFLECTABLE macro from this answer in my C++ header file, which looks like:

#ifndef TIMER_H
#define TIMER_H

// From the linked question, but (deliberately) ignored by SWIG here, 
// only relevant is as much as it defines REFLECTABLE for other preprocessors
#include "reflection.hh"

struct Timer {
  REFLECTABLE (
    (float) startTime,
    (float) endTime,
  )
};

#endif

The SWIG preprocessor doesn't follow #include and doesn't handle the definition of REFLECTABLE from the linked question anyway, so my plan is to ignore that entirely and replace it with a more appropriate SWIG specific definition of REFLECTABLE.

My goal is for the SWIG preprocessor to expand Timer as though there was nothing special going on, i.e.:

struct Timer {
  float startTime;
  float endTime;
};

To that end I have written a simple reflection.i that makes a recursive call to a %define SWIG macro:

#define REM(...) __VA_ARGS__
#define PAIR(x) REM x

%define EXPAND(tail, ...)
  PAIR(tail);
  EXPAND(__VA_ARGS__)
%enddef

#define REFLECTABLE(...) EXPAND(__VA_ARGS__)

That I can use in test.i:

%module test

%include "reflection.i"
%include "timer.h"

When we run swig -python -Wall -E test.i to inspect the result of running this through the SWIG preprocessor it almost works, but the definition at the end of recursion doesn't quite match what I'd like:

struct Timer {
  /*@SWIG:reflection.i,4,EXPAND@*/
  float startTime;
  /*@SWIG:reflection.i,4,EXPAND@*/
  float endTime;
  EXPAND
};

The problem is that recursion gets terminated by the bareword EXPAND when ___VA_ARGS__ is empty. I looked at using map.h from this answer, but that also doesn't work with the SWIG preprocessor.

How can I modify my definition of REFLECTABLE so that the SWIG preprocessor just generates the flat structure I'm looking for?

Community
  • 1
  • 1
Flexo
  • 87,323
  • 22
  • 191
  • 272

1 Answers1

1

I came up with a solution in the end that required two changes:

  1. There's more indirection to the recursion, we paste two tokens together to get the token EXPAND_STOP or EXPAND_MORE, depending on whether or not the __VA_ARGS__ is empty during this evaluation. This selection takes place because SWIG evaluates

    #define STOP(...) MORE
    

    as MORE when there are arguments, but simply as STOP when there are no arguments.

    The TOKEN macro then adds the required indirection for the token paste operator to work.

  2. There's a fake first argument to both EXPAND_MORE and EXPAND_STOP that prevents the same behaviour from stopping us eating the call to EXPAND_STOP with no arguments.

#define REM(...) __VA_ARGS__
#define PAIR(x) REM x

#define TOKEN_(x,y) x##y
#define TOKEN(x,y) TOKEN_(x, y)

#define STOP(...) MORE

%define EXPAND_MORE(fake, ...)
  EXPAND(__VA_ARGS__)
%enddef

#define EXPAND_STOP(fake, ...)

%define EXPAND(tail, ...)
  PAIR(tail);
  TOKEN(EXPAND_,  STOP(__VA_ARGS__)) ## (fake, ##__VA_ARGS__)
%enddef

#define REFLECTABLE(...) EXPAND(__VA_ARGS__)

Although possibly not the most elegant solution this is tested and working with SWIG 3.0.2.

Flexo
  • 87,323
  • 22
  • 191
  • 272