Most of this article puts a positive spin on metaprogramming. I'm happy enough to leave you with this impression, but I should also mention some problems.
The first problem is to do with trouble-shooting. You have problems with your program but the problem is actually in the metaprogram which generated your program. You are one step removed from fixing it.
I deliberately used the term trouble-shooting rather than debugging. When you think about it, debug builds and debuggers are there to help you solve these problems by hooking you back from machine code to source code. It gives the illusion of reversing the effect of the compiler. If you can provide similar hooks in your metaprograms, then similarly the fix will be easier to find.
The second problem I refer to as the quote-escape problem. It bit me recently when I converted a regular C++ program into one which was partially generated by another C++ program. For details, I refer you to the original article. For the purposes of this article, look at what happened when I needed to generate C++ code which produces formatted output.
Here's the code I wanted to generate:
context.decodeOut()
<< context.indent()
<< field_name << " "
<< bitwidth
<< " = 0x" << value << "\n";
Here's the code I developed to do the generating:
cpp_file
<< indent()
<< "context.decodeOut() << context.indent() << "
<< quote(field_name
+ " "
+ bitwidth
+ " = 0x")
<< " << context.readFieldValue("
<< quote(field_name) + ", "
<< value
<< ") << \"\\n\";\n";
It looks even worse without the helper function, quote
, which returns a
double-quoted version of the input string.
I was able to defuse this problem with some refactoring but the self-referential nature of metaprogramming will always make it susceptible to these issues.
This is also part of the reason why Python is so popular as a code-generator: as has been shown by some of the preceding examples, its sophisticated string support allows us to bypass most quote-escape problems.
I have already mentioned the problem of integrating code-generators into your build system. Some IDEs don't integrate them very well, and even if they do, we have introduced complexity into this part of the system. In general we prefer to trade complexity at build time for safety at run-time but we should always check that the gains outweigh the costs.
We're nearing the end of our investigation, and I hope the Why Metaprogram? question I posed at the beginning has been addressed. The Wikipedia answers this question rather more directly:
[Metaprogramming] ... allows programmers to produce a larger amount of code and get more done in the same amount of time as they would take to write all the code manually.
It's possible to interpret this wrongly. As we all know, we want less code, not more (more software can be good, though). The important point is that the metaprogram is what we develop and maintain and the metaprogram is small: we shouldn't have to worry about the size of the generated code.
Unfortunately we do have to worry about the generated code, not least because
it has to fit in our system. If we turn a critical eye on the
ISO 8859 conversion functions
we discussed earlier we can see that the generated code
size could be halved: values in the range (0, 0x7f)
translate unchanged into
UTF-8, and therefore do not require 128 separate case statements. Of course, the
metaprogram could easily be modified to take advantage of this intelligence,
but the point still holds: generated code can be bloated.
See Brown for a more thorough discussion of this issue.
Good programmers use metaprograms because they are lazy. I don't mean lazy in the sense of Can't be bothered to put the right header in a source file, I mean lazy in the sense of Why should I do something a machine could do for me?
Being lazy in this way requires a certain amount of cleverness and clever can be a pejorative every bit as much as lazy can. A metaprogram lives at a higher conceptual level than a regular program. It has to be clever.
Experienced C++ programmers are used to selecting the right language features for a particular job. Where possible, simple solutions are preferred: not every class needs to derive from an interface, not every function needs template-type parameters. Similarly, experienced metaprogrammers do not write metaprograms when they can, they do it when they choose to.
Copyright © 2005 Thomas Guest |