Meta Meta Macro

last modified: December 29, 2005

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).


Loading...