
Higher Order Macros in C++ - ingve
http://journal.stuffwithstuff.com/2012/01/24/higher-order-macros-in-c/
======
Rexxar
The technique used here is called "X Macro". There are article on wikipedia
and wikibooks.

[https://en.wikipedia.org/wiki/X_Macro](https://en.wikipedia.org/wiki/X_Macro)

[https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-M...](https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros)

It's very useful in some situations. It offers, in a way, a mean to do a sort
of compile-time introspection on some class/enum.

------
kazinator
Good grief, I remember this cruft.

    
    
       class Nineties : public Node {
          void accept(Visitor &v) {
             cout "Visitor " << v << ", welcome to the year 1995" << endl;
          }
       }
    

A better idea would be to write all this code processing _in_ the language
that is being byte-code interpreted. That is to say, use an existing
implementation of that language to write a compiler which produces byte code.
Then write the byte code interpreter for that in C++. Finally, include the
compiler's byte code image in the C++ program (as a static array or whatever),
so that the program is a complete implementation which can compile code and
then run the bytecode.

~~~
munificent
> A better idea would be to write all this code processing in the language
> that is being byte-code interpreted.

That presumes the language being compiled somehow frees you from the need to
use the visitor pattern.

Personally, I really like visitor. It's not a pattern I use often (basically,
only for AST visitors), but it's pretty fanstastic in the places where I do
need it.

~~~
kazinator
Visitor leads to the wrong organization of code for processing AST's. It's
better to simply have big case statement on the types of nodes you have to
process. Here is why. The case statement puts together pieces of code that are
related. The method framework, by contrast, puts together pieces of code which
do unrelated things, which are only connected by the fact that they apply to
the same kind of node. This helps you slightly if you're adding a new kind of
node, but not if you want to understand at a glance how the same kind of
process is orchestrated over forty different nodes: for that, it's better to
be staring at a case statement. I want to see all the ways that forty
different nodes are subject to type checking; I don't want to see,
simultaneously, how a single node is type-checked, pretty-printed, traversed
for constant folding, code-generated.

That kind of organization really works well when you don't care about the
other classes which do that, like device drivers. I don't care about how a tty
handles write when I'm working on a network stack's write; I don't need to see
those side by side.

Thus even if you give me a language with real OO that has multiple dispatch so
that Visitor disappears, I still won't do it this way. I'm going to have a
single AST pretty printing function that handles all the cases, a code
generating function that handles all the cases and so on. If someone adds a
case, they will just have to add them to half a dozen functions.

~~~
munificent
There must be some confusion here. Your first paragraph describes what code
looks like when you _don 't_ use the visitor pattern.

Just to be explicit, here's the kind of code I think you're describing (using
some vague Java-esque pseudo-code):

    
    
        interface Expression {
          String prettyPrint();
          int evaluate();
        }
    
        class Plus extends Expression {
          Expression left, right;
    
          String prettyPrint() {
            return left.prettyPrint() + " + " + right.prettyPrint();
          }
    
          int evaluate() {
            return left.evaluate() + right.evaluate();
          }
        }
    
        class Number extends Expression {
          int value;
    
          String prettyPrint() {
            return value.toString();
          }
    
          int evaluate() {
            return value;
          }
        }
    

This is vanilla OOP code. You have all of the operations on a class directly
on that class. Like you note, pretty printing and evaluating are mixed
together. With something like an AST, it doesn't scale and gets gross.

Here's the AST classes supporting visitor pattern:

    
    
        interface Expression {
          String prettyPrint();
          int evaluate();
    
          T accept<T>(Visitor<T> visitor);
        }
    
        class Plus extends Expression {
          Expression left, right;
    
          T accept<T>(Visitor<T> visitor) { return visitor.visitPlus(this); }
        }
    
        class Number extends Expression {
          int value;
    
          T accept<T>(Visitor<T> visitor) { return visitor.visitNumber(this); }
        }
    
        interface Visitor<T> {
          T visitPlus(Plus plus);
          T visitNumber(Number number);
        }
    

These are now pure data classes, like we want. The only extra bit is the
visitor pattern itself.

Now we can separate out our concerns. Here's pretty-printing:

    
    
        class PrettyPrintVisitor extends Visitor<String> {
          String visitPlus(Plus plus) {
            return plus.left.accept(this) + " + " + plus.right.accept(this);
          }
    
          String visitNumber(Number number) {
            return number.value.toString();
          }
        }
    

And here's evaluating:

    
    
        class EvalVisitor extends Visitor<int> {
          int visitPlus(Plus plus) {
            return plus.left.accept(this) + plus.right.accept(this);
          }
    
          int visitNumber(Number number) {
            return number.value;
          }
        }
    

> I'm going to have a single AST pretty printing function that handles all the
> cases, a code generating function that handles all the cases and so on.

This is exactly what visitor gives you except replace "function" with "class".

> If someone adds a case, they will just have to add them to half a dozen
> functions.

They'll have to _remember_ to do that. The visitor pattern makes it a compile
error to forget to do that.

I also find the pattern scales up better than doing all of the type tests
inside a single enormous function. I like having a separate method per AST
type.

------
osullivj
Very powerful technique. However, one of my C++ coding rules of thumb these
days is 'can I step through it in the debugger?'

~~~
agumonkey
It's a great rule for any metaprogramming task. When writing a macro, or
reflective intercession, make it a shim that wraps non-metalevel code, so it's
decoupled, testable and yes debuggable.

~~~
ra88it
Interesting. Do you have an example or a link to an example?

~~~
agumonkey
I can't find the exact quote nor site, but it's a common advice in lisp
tutorials to write the macro as thin as possible and split its internal logic
as tiny composable functions (the usual desired lisp/engineering style).

The rest comes from my small experience, in college we had a project patching
java bytecode on the fly, you could generate correct logic at the bytecode
level, but you would lose all the benefit of high level language
(typechecking, debugging). One team had this excellent idea to just write the
smallest bytecode hook/wrapper that would then call normal Java code.

------
makecheck
I prefer writing scripts to generate repetitive code. The result tends to be
quite readable, it can be seen in the debugger, and you have the full power of
a scripting language to build your code.

~~~
munificent
Yeah, when the language isn't flexible enough to let me get rid of boilerplate
using its own primitives, I like doing that too. AST classes are a good
example of something where a simple script can pound them out.

When I was working on Magpie, I did just that. This script[1] generates this
file[2].

[1]:
[https://github.com/munificent/magpie/blob/master/script/gene...](https://github.com/munificent/magpie/blob/master/script/generate_ast.py)
[2]:
[https://github.com/munificent/magpie/blob/master/src/Syntax/...](https://github.com/munificent/magpie/blob/master/src/Syntax/Ast.generated.h)

I got this trick from Jim Hugunin, of Jython and IronPython fame.

------
PatrickAMoran
There is a whole library around this and similar techniques in Boost. I've
used it before on several occasions. It's quite nice, although it somewhat
lays a trap, in the sense that CPP really should be your metaprogramming
technique of last (or second-to-last) resort, so it remaining difficult keeps
your incentives in line. :)

I've never used Chaos PP (the predecessor of Boost.Preprocessor), but IIRC, it
enables even more cool stuff at the cost of reduced portability
(Boost.Preprocessor contains all the hacks to work on broken preprocessors,
while I think Chaos takes more of a "fix your preprocessor" approach).

[http://www.boost.org/doc/libs/1_59_0/libs/preprocessor/doc/i...](http://www.boost.org/doc/libs/1_59_0/libs/preprocessor/doc/index.html)

------
jordigh
Is there a template alternative? Whenever macros seem essential in C++,
there's usually a better way without macros.

~~~
usefulcat
If there is I'd love to see it. But I'm fairly certain there's no way to do
what he's doing with templates.

~~~
striking
Also, templates can be visually unappealing and slow to compile.

(Of course, these are generalizations, and only how they work without
particular care.)

------
chubot
Meh, why not just use code generation for this problem? That's what CPython
does.

[http://eli.thegreenplace.net/2014/06/04/using-asdl-to-
descri...](http://eli.thegreenplace.net/2014/06/04/using-asdl-to-describe-
asts-in-compilers)

To me this [1] looks way better than a bunch of C++ macros.

[1]
[https://hg.python.org/cpython/file/tip/Parser/Python.asdl](https://hg.python.org/cpython/file/tip/Parser/Python.asdl)

~~~
usefulcat
The advantage of his approach is that the C preprocessor is already present,
so no need to install any additional external tools. The stuff he's
demonstrating is simple enough that using an external tool would be overkill.

~~~
makecheck
The preprocessor doesn't necessarily behave though (just look at Visual
Studio). You might actually have a more consistent result by using the same
version of a scripting language on every platform.

~~~
MereInterest
That seems more of a reason to upgrade your tools, rather than adding a new
tool. Granted, that may be easier said than done, as broken compilers may
accept broken code that needs to be fixed.

------
joshguthrie
Anecdote: In BS school, we had 48 hours to write a "C-with-some-kind-of-
objects". My code ended up being 75% macros and we got bonus points for
abusing the preprocessor.

------
recentdarkness
Funny, this is exactly what I am doing in such cases - I don't even have to
look into any sources of others to make that up. Probably a long time
experience that this is for me nothing I need to tell anyone about in a blog
post.

I use this for dispatching, enum creation, method creation etc.

But probably a good resource for people trying to save their time by macro
based code generation ;)

------
bakhy
upvoted for the angry Bruce Lee comparison!

