Meta Meta Macro

If you want to take advantage of MetaMacros in your CeeLanguage/CeePlusPlus code, you might find that the MetaMacro implementation involves a lot of repetitive typing, so you could then decide to autogenerate your MetaMacro header files, presumably the result would be expressed as MetaMetaMacros. The RubyLanguage code at the bottom of the page will create a C99-compatible header file called meta.h, which defines the following MetaMacros:

  metaIDENT(X_)
    expands to X_ (identity metamacro)
  metaCOMMA(X_)
    expands to X_,
  metaSEMICOLON(X_)
    expands to X_;
  metaLPAREN(X_)
    expands to (X_
  metaRPAREN(X_)
    expands to X_)
  metaPARENS(X_)
    expands to (X_)
  metaJOIN(X_, ...)
    joins the following X_ arguments together with ##
  metaLOOPn(E_, M_, X_)
    applies macro M_ to X_ E_ times.
    the character 'n' should be replaced by an integer 0-7 (to allow loops in loops)
  metaINC(X_)
    expands to the result of X_ + 1 (X_ is integer 0-63)
  metaDEC(X_)
    expands to the result of X_ - 1 (X_ is integer 1-64)
  metaADD(X_, Y_)
    expands to the result of X_ + Y_ (X_ and Y_ are integers 0-63, result ranges 0-63)
  metaSUB(X_, Y_)
    expands to the result of X_ - Y_ (X_ and Y_ are integers 0-63, X_ > Y_, result ranges 0-63)
  metaLISTn(E_, T_, S_)
    expands to the result of applying T_ to the list of integers 0-E_, applying S_ to provide separation.
    the character 'n' should be replaced by an integer 0-7 (to allow lists in lists)
A brief example:
  metaLIST0(10, metaPARENS, metaCOMMA)
expands to:
  (0), (1), (2), (3), (4), (5), (6), (7), (8), (9), (10)
A more complex, (and more conceivably useful) example:
  #define TYPENAMES(X_) typename metaJOIN(2, P, X_)
  #define PARAMS(X_) metaJOIN(2, P, X_) metaJOIN(2, p, X_)
  #define ARGS(X_) metaJOIN(2, p, X_)

#define MY_TEMPLATE_THUNK(X_) \ template<typename R, metaLIST0(X_, TYPENAMES, metaCOMMA)> \ R thunk(metaLIST0(X_, PARAMS, metaCOMMA)) \ { \ return func(metaLIST0(X_, ARGS, metaCOMMA)); \ }

metaLIST1(9, MY_TEMPLATE_THUNK, metaIDENT)
produces:
  template<typename R, typename P0>
  R thunk(P0 p0)
  {
      return func(p0);
  }

template<typename R, typename P0, typename P1> R thunk(P0 p0, P1 p1) { return func(p0, p1); }

//...

template<typename R, typename P0, typename P1, typename P2, typename P3, typename P4, typename P5, typename P6, typename P7, typename P8, typename P9> R thunk(P0 p0, P1 p1, P2 p2, P3 p3, P4 p4, P5 p5, P6 p6, P7 p7, P8 p8, P9 p9) { return func(p0, p1, p2, p3, p4, p5, p6, p7, p8, p9); }
I knocked up the code in a couple of hours mainly for kicks. It'd be interesting to see if anyone has any suggestions for improvements. I think there's a technique used by the BoostLibraries to allow expansion of nested macros without needing an explicit 'n' term (such as in metaLIST, metaLOOP), but I wasn't able to unpick exactly how they did it...


  class MetaHeaderFile

def initialize filename @header = 'META_h' @pre = 'meta' File.open filename, 'w' do |@file| write end end

def with_header_guards &block @file.puts <<-EOQ #ifndef #{@header} #define #{@header} EOQ block.call @file.puts <<-EOQ #endif//#{@header} EOQ end

def write_join num n = 1 args = "X0_, X1_" macro = "X0_##X1_" while n < num n += 1 @file.puts "#define #{@pre}JOIN_i#{n}(#{args}) #{macro}" args += ", X#{n}_" macro += "##X#{n}_" end @file.puts "#define #{@pre}JOIN_i(X_, ...) #{@pre}JOIN_i##X_(__VA_ARGS__)" @file.puts "#define #{@pre}JOIN(X_, ...) #{@pre}JOIN_i(X_, __VA_ARGS__)" end

def write_loop num, extent num.times do |n| extent.times do |e| if 0 == e macro = 'X_' elsif 1 == e macro = 'M_(X_)' else macro = "M_(#{@pre}LOOP_i#{n}_#{e-1}(M_, X_))" end

@file.puts "#define #{@pre}LOOP_i#{n}_#{e}(M_, X_) #{macro}" end @file.puts "#define #{@pre}LOOP#{n}(E_, M_, X_) #{@pre}JOIN(4, #{@pre}LOOP_i, #{n}, _, E_)(M_, X_)" end end

def write_maths extent extent.times do |e| @file.puts "#define #{@pre}INC_i#{e} #{e+1}" @file.puts "#define #{@pre}DEC_i#{e+1} #{e}" end @file.puts "#define #{@pre}INC(X_) #{@pre}JOIN(2, #{@pre}INC_i, X_)" @file.puts "#define #{@pre}DEC(X_) #{@pre}JOIN(2, #{@pre}DEC_i, X_)" @file.puts "#define #{@pre}ADD(X_, Y_) #{@pre}LOOP7(Y_, #{@pre}INC, X_)" @file.puts "#define #{@pre}SUB(X_, Y_) #{@pre}LOOP7(Y_, #{@pre}DEC, X_)" end

def write_list num, extent num.times do |n| extent.times do |e| if 0 == e macro = 'T_(0)' else macro = "S_(#{@pre}LIST_i#{n}_#{e-1}(T_, S_)) T_(#{e})" end

@file.puts "#define #{@pre}LIST_i#{n}_#{e}(T_, S_) #{macro}" end @file.puts "#define #{@pre}LIST#{n}(E_, T_, S_) #{@pre}JOIN(4, #{@pre}LIST_i, #{n}, _, E_)(T_, S_)" end end

def write with_header_guards do @file.puts "#define #{@pre}IDENT(X_) X_" @file.puts "#define #{@pre}COMMA(X_) X_," @file.puts "#define #{@pre}SEMICOLON(X_) X_;" @file.puts "#define #{@pre}LPAREN(X_) (X_" @file.puts "#define #{@pre}RPAREN(X_) X_)" @file.puts "#define #{@pre}PARENS(X_) (X_)" write_join 16 write_loop 8, 32 write_maths 64 write_list 8, 32 end end end

MetaHeaderFile.new('meta.h')


A macro-free version of the same idea can be found in Alexandrescu's ModernCeePlusPlusDesign.

Could you expand on this comment? One of the examples I gave (above the code) expands to a set of template functions, each with one parameter more than the previous. I'm not sure how its possible to automate such a thing without using the preprocessor (at least until variadic templates become part of the C++ standard). I guess I'll have to read the book... If you're making the point that TemplateMetaprogramming is better than PreprocessorMetaprogramming?, then I'd broadly agree with you (although string processing is actually harder with template metaprogramming).


EditText of this page (last edited December 29, 2005) or FindPage with title or text search