C++ Template Meta Programming is Still Evil

I won’t include a meta-programming intro paragraph here, since if you’re not familiar with it – I sincerely hope you stay that way. If you insist, get an idea online or read the book (it’s a good read, but can’t say I recommend it since the entire purpose of this post is to persuade you to not use what it teaches).

I don’t like meta-programming. Passionately so. What’s worse, I seem to be pretty much the only one: I can’t really find any anti-MP texts around!  So either

(a) There’s a community of MP-bashers lurking somewhere out of my reach,

(b) I’m waaaay off, and comments to this post would make me see my wrongdoing and shy away to a dark cave for a while,

(c) The world really misses an anti-MP manifest.

However the case turns out it would do me good to try and articulate these thoughts. Here goes.

Meta-Programming is Hacking, not Engineering

One might refer as hacking to pouring orange juice using chopsticks, cutting toothpastes open, or amplifying phones with glass pitchers.  On the less-adorable side, we also refer as ‘hacks’ to chewing gum car fixes. Here’s a suggested definition that tries to encompass the creative, improviser, and often lazy aspects of hacking:

Hacking: Achieving a goal by using something in a way it wasn’t designed for.

Defining the other side of the scale is much easier:

Engineering: the discipline, skill, and profession of acquiring and applying scientific … and practical knowledge, in order to design and build  …  systems and processes.

Now how would you classify meta-programming?   It’s inception, for one, gives a clear hint:

Historically TMP is something of an accident; it was discovered during the process of standardizing the C++ language that its template system happens to be Turing-complete, i.e., capable in principle of computing anything that is computable.

C++ types were never designed to perform compile time calculations. The notion of using types to achieve computational goals is very clearly a hack – and moreover, one that was never sought but rather stumbled upon.

The Price

Don’t take it from me, take it from two guys who know an itzy bit more about C++.

Herb Sutter, former secretary of the C++ standardization committee, is one.

Herb: Boost.Lambda, is a marvel of engineering… and it worked very well if … if you spelled it exactly right the first time, and didn’t mind a 4-page error spew that told you almost nothing about what you did wrong if you spelled it a little wrong. …

Charles: talk about giant error messages… you could have templates inside templates, you could have these error messages that make absolutely no sense at all.

Herb: oh, they are baroque.

Jim Radigan, MSVC compiler lead developer, probably understands a thing or two himself.

Jim: We’ve been able to use templates, we’ve been able to do a whole bunch of things.

Charles: Do you use advanced sort of meta-programming at the compiler level?

Jim: We try to steer away from really complex things like that because what happens is.. the tire hits the road when it’s two o’clock in the morning and somebody sends you a pri-zero bug, say, windows doesn’t boot.  …

so what we engineered for, clearly, is maintainability. You want somebody to come up to speed, be able to go in, binary search windows and step through the debugger in the compiler and find out where we did the illegal sequence.  …

One of the other things that happen when we go to check code into the compiler is we do peer code review. So if you survive that, it’s probably ok, it’s not too complex. But if you try to check in meta-programming constructs with 4-5 different include files and virtual methods that wind up taking you places you can’t see unless you’re in a debugger – no one is going to let you check that in.

… We do use STL, but we don’t go really abstract because we want to be able to quickly debug.

So bottom lines,  using meta-programming you end up pouring substantially more effort into writing code that even builds.  Your code maintainers end up pouring substantially more effort to be able to understand and debug that code.

The Benefit

Concise, elegant code.

As far as I can say, that is the sole benefit of this ordeal.

Think about that for a second. The very real reward for using MP in your code is the moment of satisfaction of having solved a hard riddle. You did stuff in 100 lines that would have otherwise taken 200.  You grinded your way through incomprehensible error messages to get to a point where if you needed to extend the code to a new case, you would know the exact 3-line template function to overload. Your maintainers, of course, would have to invest infinitely more to achieve the same.

I whole heartily empathize – I can get lost for days in such riddles (in and out of programming), and I still remember the joy of having first deciphered a SFINAE construct in code.

It might be a necessary stage in every developers professional path, but one must mature out of it. You have to return and think of your tools as exactly that, tools: unless you’re a standard committee member C++ is a means to an end, not a goal by itself. The geeky pleasure of having mastered the esoteric side effects of some language features is completely understandable, but engineering-wise the price can be formidable – so please, please, fight this temptation valiantly.

Perhaps some day..

The original post title was the way-more-catchy ‘MP is evil’. I modified it to ‘Still Evil’ because I have high hopes: C++11 seems to be very aware of the desire to make compile-time programming a designed language feature, and not just a collection of library hacks.

Let’s talk again in the future. I’ll be very open to revise my opinions when concepts are standardized and any compiler implements constexpr.

Advertisements
This entry was posted in C++. Bookmark the permalink.

33 Responses to C++ Template Meta Programming is Still Evil

  1. lisu says:

    I think, I do agree with most of your points. But then, I cannot deny there is a pure beauty, when MP really has been useful and good design decision. For me example of such situation is boost implementation of CRC http://www.boost.org/doc/libs/1_35_0/libs/crc/crc.html .

    • Ofek Shilon says:

      @lisu – I’m afraid that’s exactly the state of mind I’m going against: using code for its being aesthetically pleasing, instead of being maintainable. I never used boost’s CRC, but have used e.g. BOOST_FOREACH and BOOST_LAMBDA – and was resolved never to use them again, for the reasons mentioned in the post. Their usage forms elegant code, and internally they use exceptionally clever tricks, but you and your maintainers can’t get nearly-informative error messages if you tweak something wrong, and you and your maintainers would have to invest A LOT to understand the implementation nuances and limitations. Apparently someone in the C++ committee had similar thoughts, as both foreach and lambda are now language features (that I’m all up for).

  2. Wyatt says:

    I gather (from your page title and the nature of your arguments) you’re only talking about C++ TMP? In which case, agreed. On the other hand, it seems shortsighted to skewer an entire paradigm based solely on a single attempt saddled with baggage. Instead, why not look at D, which manages a much higher degree of expressive elegance?

    • Ofek Shilon says:

      @Wyatt: Yes, I’m talking about C++ – turns out it’s less obvious than I hoped. I’ll go edit the post title. Thanks!

      • Wyatt says:

        Seeing the reply after mine…oof, I see what you mean. Oh, on, “I can’t really find any anti-MP texts around”, I think at least for C++ no one sane argues in favour of C++ templates in general. Even Andrei Alexandrescu seems maybe a bit mortified to be known as one of the “C++ people” and we all know what he wrote….

  3. deadalnix says:

    I’m afraid that this post is rather ignorant.

    First of all, you don’t make any difference between C++ template and metaprogramming. Stating that metaprogramming is bad because C++’s template system is terrible isn’t really an argument against metaprogramming.

    You may want to consider LISP, that is way older than C++, and provide way more powerful metaprogramming capabilities. On the other hand, you may want to look at D, that provide a lot of compile time capabilities, in a way nicer way than C++ does.

    In fact, C++ template system can lead to really messy stuffs, but that tells more about C++ than about metaprogramming.

    • Ofek Shilon says:

      @deadalnix: Yes, I’m talking about C++. I was kind of hoping it would be obvious from the name of my blog, the category of this post, and every other line in it. I was hoping even more the comments would have at least something to do with the actual contents of the post.

      • deadalnix says:

        OK, then we do agree, C++ metaprogramming capabilities are terrible. Your blog post have been posted in several places where the discution isn’t really about C++ specifically. I didn’t knew your blog before and the title is misleading.

  4. jhgarvin says:

    Uh, clang and GCC both support constexpr already:

    http://clang.llvm.org/cxx_status.html
    http://gcc.gnu.org/projects/cxx0x.html

    And concepts were dropped from C++11 (though they may make it in in a future standard). Anyway, C++ TMP is generally complicated, and that maybe partially a result of the extent of its power not being clear when templates were first added to the language. However, titling your post ‘still evil’ seems to imply you think C++11 hasn’t gone some way towards making it less complicated, which I think it has, constexpr being one of the big reasons. Also, you have to consider the alternative on a case by case basis. If you don’t use C++ TMP to solve a particular problem, are you using a code generator instead? A code generator that will make your build system more complicated, probably not interact well with all the language features, and make hard to debug problems worse because the compiler isn’t aware of your generation step? If so, C++ TMP can be the lesser evil, if you want to regard it as one.

    • Ofek Shilon says:

      I’m well aware of the status of concepts. Didn’t know clang/gcc now implement constexpr (thanks!) but I believe until MS/Intel implement it you couldn’t call it mainstream, or use it in library code (that’s intended to be portable). I also elaborated specifically about the ‘still’: the C++ standard *still* doesn’t do concepts, and market-share-wise most of the compilers *still* don’t do constexpr. And I do hope to see both change soon.

      • jhgarvin says:

        Intel has already implemented it too. MSVC is the only straggler. There are 3 compilers out there, 2 of which are free, that should be plenty for you to reevaluate whether you think TMP is still evil ;)

  5. None says:

    I think there are some cases were you gain some performance. AFAIK blitz and armadillo linear algebra libraries use it for loop unrolling and other optimizations.

    • Ofek Shilon says:

      I’ve yet to come across such a case myself, and the sort of arguments that I do encounter at work have to do only with the elegance of TMP code. But regardless – I respect that argument, and if that is the case in your context I agree it is a valid use.

      • deadalnix says:

        A very common example is qsort vs STL sort function. qsort have to copy item byte by byte, because item size is a runtime parameter, where sort can maximize, which is slower.

        But I have to agree that the problems caused by C++ template system often don’t justify the usage.

  6. Ben Hanson says:

    Any feature can be abused and it should be part of a code review to justify the techniques employed. However, just like any other technique TMP in C++ can be completely justified when the gains outweigh the disadvantages.

    I would say the biggest problem with extensive/excessive TMP usage is slowing the build to a crawl. I agree that C++ needs much better TMP support and that constexpr is a good start.

  7. Alan Ning says:

    You are certainly not the only one who dislike metaprogramming. After programming 7 years in C++, I can say that I am only comfortable at using a small subset of C++.

    Boost is particularly the worst offender when it comes to MP. Everything in Boost must be in overengineered templates, even when there are far cleaner solutions out there. (Tokenizer, I am looking a you).

    For the past two years, I took a step back and started writing Java, C# and Python. Working with simpler languages makes me realize how much time I’ve wasted solving irrelevant C++ only problems.

    You are not alone. :)

    … Alan

  8. Jim says:

    Late to the party, I know, but I think the power of TMP is displayed by an observation that the most widely used and respected libraries available make heavy use of it. STL, BOOST, Blitz, etc. I know of no widely used C++ libraries that don’t. The rest are C.

    TMP like any other feature can run from simple tests for pointer vs reference, etc to full on code generators like Spirit. It’s a bit like saying inheritance is evil. C++11 has fixed a lot of problems with templates, but opened up new venues for use and abuse. Using auto to vary the return value of a function based on a template parameter for example.

  9. nachochip123 says:

    Thanks for the article, I totally agree what you said. As an engineer working in the game industry more than a decade,
    I still stay away using template unless it provide an absolutely better solution and simpler code. When I said simpler code,
    it is not just the interface but also the simplicity to understand and debug. Most of the time, debugging template code is much more difficult due to the metaprogramming nature.

    I cannot denied that the TMP is a very powerful tool and there a lot of fancy usages. However, those fancy techniques sometimes also mean making the code more difficult to understand. For example, I never have fun time when fixing any compile error with the BOOST library. The BOOST library may provide a very elegant interface when dealing with some problems, but debugging its source is a totally different story. Btw, I have nothing against the BOOST library, it just not my cup of tea.

    In my experience, the overuse of the template heavily impact the compile time as well as increase the binary size. This side effect is always a big no no in the game industry. In respond to Jim, there are a lot of libraries which are not C but didn’t use template as much as Boost or STL. Most commercial grade game engine, eg Gamebyro, Unreal, Cry Engine, Source Engine, Havok etc, and a lot of good open source engines like Irrlicht, Ogre, Cocos2D, Box2D are not TMP heavy. The TMP code mainly around the generic container class or may be around the smart pointer class.

    Another argument I want to make is that when compare C++ with Java, which IMO is a heavily OO driven language, there are no template or similar feature provided in its language specification. It is because Java make everything subclass from Object which eliminated the necessity of using template. I think it is a better OO approcah to solve the fundamental problem.

    In conclusion, I did’t against metaprogramming or any other language features as long as it provides simpler code both for understanding and debugging. After many years in the industry, I just want something simpler and get the thing done rather than doing too much fancy moves.

    • Jim Edwards says:

      Those libraries are a great example of my point. Most of those libraries are actually engines. A library, at least a good one, allows a user to take only pieces of the library and mix them with other libraries. I can’t take an ogre mesh and render it with Gamebryo. I can take a std list of Blitz vectors and use a boost algorithm to process them. That’s the power of templates. Many of those game engines make use of good libraries for loading and processing images or process sound, but those are C style libraries.

  10. John Morrison says:

    What is interesting is using the techniques on which template metaprogramming is built to create richer more intelligent typing. This can be done while keeping code perfectly readable by any dilligent software engineer. TMP isn’t the only way to write opaque code. I agree with you, I avoid building my code on libraries if I can’t even begin to understand how they work

    • Ofek Shilon says:

      @JohnMorrison: surely TMP can create *readable* code, just not *maintainable* code. There is a difference – code can (and often does) have a proper outer-layer, that makes reading it manageable. But maintaining code is more than reading it – it is diving in when something doesn’t go as expected. And that is where TMP, more often than not, IMHO is a wrong choice.

  11. Nir says:

    I am curious in this discussion, where the line is between “templates” and “TMP”. I’m sure that when you write containers, you template them on the type. It’s no fun to write HopscotchHashDouble, HopScotchHashInt, etc etc. So where is the line?

    Templates can be used to do many things that are powerful, but hard to maintain and understand. They can also do very powerful things with just moderate complexity. I cite typical usage of enable_if (and type traits, as they’re often used in conjunction) as powerful, desirable functionality, with a very moderate cost in complexity/maintainability. Do you disagree with this assessment?

    It seems you are putting on all of TMP the label I would put on e.g. Boost Spirit. But I don’t know what exactly TPM includes, in your usage of the term.

    • Ofek Shilon says:

      That is a good and very relevant question. Personally I would draw the line far before Boost Spirit – I’d say even std::function is an offender: http://stackoverflow.com/questions/25841857/vc2013-function-from-bind-not-compiling
      Templates were designed for containers. Type traits and even enable_if are very useful in that context (containers)- but I’d dare say anything beyond that context is a hack.

      • Nir says:

        Type traits and enable_if are very useful even outside of the context of containers; they have nothing to do with containers per se but allow you tremendous control over implementation by type while keeping interface simple and homogeneous, all with zero runtime cost. You could have two implementations for a stand alone function, one for primitives and another for complex classes. There is no other way (e.g. overloading) that’s as good to do this.

        I don’t use std::function much. I’m sure it can be a pain in the ass to use, but it is allowing you to do something that isn’t that easy to do in C++: have runtime polymorphism without inheriting from a common base class. Kind of like boost any. It’s true that in most cases I would avoid this in my design, but especially when you’re writing library level code, you may not want to restrict what kind of callable a user passes into a HOF, and compile time polymorphism may not be enough.

        The problem I guess I have is that your statement is very broad and general, and it’s unclear that it actually is reasonable to avoid TMP in the majority of cases. For instance, people often say “arrays are evil” in favor of std::vector. And about 99% of the time you could use either, the std::vector is the right choice (probably more actually). So while arrays do have their uses, it’s a reasonable generalization.

        I think you also overemphasize the entire “what was intended” aspect of templates. I think this is a non sequiter. The strongest statement I could probably get on board with is probably something like: be wary when there is code that is using types to represent something else in a non-trivial way. What I mean by that is when you e.g. compute fibbonaci at compile time, the “types” are technically types from a C++ point of view, but their meaning actually consists of the value of a function at various points. By non-trivial, I am excluding e.g. the very simple true type and false type used in enable_if and similar constructions. But a huge amount of TMP (including std::function) is using TMP to figure things out about types, but not necessarily containers. I guess this is the part of the spectrum where we disagree the most.

        • Ofek Shilon says:

          @Nir – these are all good points, and I think I can relate to most of them. I don’t have a clear cut as to when TMP code is offensive, but I do resent the state of mind of using it because it is ‘modern’ (fancy way of saying ‘cool’) – which I encounter a lot, both on- and off-line.

          Regarding std::function in particular: I do see some usage around, and I have yet to see a case where the added flexibility (over raw function pointers) is justified by the landmass of error spew when you get a character wrong. But this – and any other specific example – is a certain basis for flame wars which I prefer to avoid. I’d be content just to communicate what I consider to be the proper state of mind when using these tools: just don’t try to be clever.

  12. John Morrison says:

    I think the description of what we are offended by is opaque. It may be opaque because of strange idioms, deep recursion or strangely abstract class names. I think this has more to do with an obsessive attitude that doesn’t know how to stop seeking further abstraction. There is a lot that can be done with modest use of the techniques on which TMP is built and I think this may be a healthier and more accessible area for programmers to work in. This is exemplified in a units of measurement library I recently published on The Code Project:

    http://www.codeproject.com/Articles/791511/Units-of-measurement-types-in-Cplusplus-Using-comp

    The code is quite readable, though you do have to realise that most of it is compile time bookkeeping that eventually generates minimal code.

    There are some problems that can only be solved using compile time template techniques or even template meta-programming. If that is the case then that is what you do but I think each of these adventures should be self contained. What scares me is the drive for code reuse and the ultimate generic abstraction has resulted in the code for a simple smart pointer depending on a spaghetti of dependencies on opaque TMP code distributed in various other header files. Of course you give up trying to figure out how it works. I don’t mind using code that without knowing how it works, but I don’t like the idea that I will never know how it works. Even horrible low level byte wise or bitwise code can be figured out if you have the time and follow all the calls, but opaque TMP is another matter.

  13. Andras says:

    Ofek, I have very similar ideas. Templates weren’t designed for this, it’s like using the screwdriver to hit a nail. You can achieve real useful, almost impossible things, but you should not base your success on having genius people who can decipher and maintain the code but on proven industry standard practices.

    What I don’t really understand is how come that nobody so far came up with a “real” C++ metaprogramming scheme, which accomplishes the same, but in a human readable/writable form.

  14. Anonymous says:

    “In traditional programming it is very common to find oneself trying to achieve the right balance of expressivity, correctness, and
    efficiency. Metaprogramming often allows us to interrupt that classic tension by moving the computation required for expressivity
    and correctness from runtime to compile time.” Templates should only be used when really necessary…

  15. Anonymous says:

    constexpr is well established by now, but IMO your post is still as true as ever. We have only a few developers who are very much into templates and use them for everything. The code is poorly understood by most others, and instead gets cargo-cult used (copy/paste from another place where the templates are used).
    When its time for a new compiler or new version of Boost, you can be sure that the templates can’t be compiled, but only the original authors can fix them (others kind of don’t want to go through the pain to understand those templates).
    And of course, compile time has gone through the roof and debuggers are a huge pain to use due to the size of the symbol table they have to parse on start-up. Triggering the occasional compiler bug is also not that good of a sign.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s