Fog Creek Software
g
Discussion Board




C++ meta programming.

I've been using C++ for a long time now.  Ok say 10 years.  I have to say I am not comfortable with where the community is going with meta programming.  My colleagues have really taken an interest in it, but to me a lot of it is really to show mental prowess or something, not to write better code.

The game to me seems to take these obscure language constructs and bend them in a way that their creators never intended to get a completely different effect.  Often the syntax is very strange.

My biggest gripe is that I feel things that could be handled in a clean, dynamic mechanism all of a sudden take on the arcane properties.  The code becomes unreadable, yet this done in the name of "libraries."  Right once, use many times.  Well I found that the libraries are harder to implement and use, which really negates their benefit.

Concept checking is my biggest pet peave.  Unlike interfaces the compiler doesn't enforce concepts.  This is horribly bad and results in compiler errors that seem to have nothing to do with the code you are writing. 

Does anyone know of a document that argues from both sides in a knowledgable manner?  My side has no support around here. 

christopher baus (www.baus.net)
Wednesday, November 12, 2003

I agree with you 100% about many people taking this stuff too far.  I've run into far too many programmers with a brand new copy of "Modern C++ Design" on their bookshelf who are just itching to apply meta-programming to SOMETHING so they go and use it all over the place, in instances where a simple straightforward solution would work better in terms of being more easily understood by a wider variety of programmers and less likely to trip up compiler bugs due to using obscure C++ features.

Mister Fancypants
Wednesday, November 12, 2003

I agree with you 100%.  Template metaprogramming is an interesting exercise (and IMO has not been proven to be anything more than that), but I don't think it belongs in a business codebase.

Trying to program like that rather than toward the average programmer just shows selfishness.  Someone who stumbles upon such code 5 years later is going to be pissed...

Also, it basically sounds like a way to make your build times enormous.  You can do anything you can with templates with runtime operations, and to insist on doing everything at compile time is premature optimization.  Sure you get extra type-checking, but I think this is outweighed by all the obscure error messages.

Roose
Wednesday, November 12, 2003

I have to add my agreement to this!

In coding for the Symbian platform (a platform written entirely in C++) I've come to appreciate their minimal but effective use of C++.  For example, they use templates to add type-safety to container classes.  The template classes to not contain any code; they inherit from a base class which provides the storage.  You get all the advantages of template type-safety without the code bloat (and strange errors) that come from using them.

I don't have any problem with meta-programming in general -- it can be very handy.  But not in C++ w/ templates; it creates more of a mess than it solves.

Almost Anonymous
Wednesday, November 12, 2003

templates are one thing.  the crap you see in Modern C++ design is a whole different ballgame.


Wednesday, November 12, 2003

I guess one person's garbage is another person treasure.  I don't think Alexandrescu's work is crap.  Far from it.  But I do think it is Academic, and would be better served by a language that was meant to support meta programming from the start. 

What really bothers me is defining "concepts" where interfaces (pure abstract classes in C++) would suffice. 

for instance:

template <typename foo>
class bar
{
  void reallyCuttingEdgeOperation()
  {
    _foo. doIt();
  }
 
  foo _foo;
}

What I hate about this is that foo can only be of a type that supports the doIt() "concept", but that concept is implicit.  It is not documented anywhere in the code other than the fact that a member called reallyCuttingEdgeOperation() calls it. 

Now when a client creates a class type to pass to type template argument foo that doesn't implement the doIt() concept, the end up with an error in bar and not in their own code. 

This sucks and reduces productivity and doesn't increase it.

christopher baus (www.baus.net)
Wednesday, November 12, 2003

C++ Metaprogramming is an interesting hack, but a horrible practice. In all cases I've seen it applied in real life, it was the wrong tool for the job.

They result in undebuggable code, incomprehensible error messages, punchcard-like compile times, and various other illnesses.

You need metaprogramming? Use a language that does it natively, like Forth or the various Lisps (Use Dylan or Pliant if you dislike the parentheses - but it really _is_ easy to get used to them if you just try).

You need code generation? Use the right tool for your job (Lex; Yacc; Lemon; D'Parser or friends for parsing; Libero for state machines; Swig for wrappers; ....). Or write your own code generator in a language that makes it easy, e.g., Perl or Python.

No, none of these solutions easily replaces Blitz. That does not necessarily mean metaprogramming is the right solution - it probably means that the right tool has not yet been made. In one project, I dropped Blitz in favor of plain-old C, because a rebuild took several hours, and an incremental build often took several minutes. With plain-old C, the compile times are seconds/incremental, and minutes for a complete rebuild.

Ori Berger
Wednesday, November 12, 2003

I have a wierd feeling that the C++ community might be the cause of the language's own demise.  In comp.lang.C++.moderated you will practically get booed off the board if you question the powerful group think.  Yet in my opinion the what the group is thinking isn't useful to the average programmer. 

repeat after me.

STL algorithmns good.
for loops bad.

boost tuple good.
abstract base classes bad.

boost any good.
void* heresy.

and so on.

christopher baus (www.baus.net)
Wednesday, November 12, 2003

That's an excellent argument against STL algorithmns, boost tuple, and boost any.

K
Wednesday, November 12, 2003

Chris,

I am SO with you there bro.

Modern C++ programming techniques have totally gone to shit. Code is absolute crap with all this metaprogramming BS. And the people who do it have elevated it to the level of some sort of cult or fringe religion. it's amazing you have to look for an article arguing both sides -- it's self evident that poor programmers use these techniques and the results are DISASTER.

Dennis Atkins
Wednesday, November 12, 2003

propellerheads' reason makes heavy use of template metaprogramming in C++, and their app is tight. so , it might have its place.

wormser
Wednesday, November 12, 2003

I agree 200% with everyone.  Every time I look at the discussions on comp.lang.c++.moderated, which I do less and less frequently, they seem to be devoted to finding ever more insanely complicated ways of doing the simplest of things. And if you really want to give yourself apoplexy, take a look at the Boost Lambda library.  But what would I know. I still write my loops starting with

for (int i = 0; i < n; i++)

whereas advanced practioners now consider it "better" to write:

for (int i(0); i != n; ++i)

There is only one side to this question. Your colleagues are all mad. Ignore them.

as
Wednesday, November 12, 2003

I'm pretty surprised about this backlash.  I'll agree that metaprogramming can be taken way too far (and I've seen that in real-world practice) but, like any other tool, when used properly, it has its place.

In response to Christopher, many times an abstract base class is the right tool for the job, but not always.

Here's an example.  We've created a "serializable" concept, which is simply a set of three methods which can be used to flatten objects to a byte stream, or restore an object's state from a byte stream.  This parallels Python's "Pickle" concept, and has the obvious useful applications (send any object across the network, write any objet to file, etc.)

One great advantage of a serializable concept over a serializable interface is that we can add the concept to all of our old classes.  Because the functions are non-virtual, adding them to old classes does not change the bit pattern, and the classes remain binary compatible.  Sometimes we pass these classes in interfaces or store them in cross-process shared memory, so this is unacceptable.  Adding support for "ISerializable" would create virtual tables on our old classes, causing all sorts of fun explosions.

Greg Falcon
Wednesday, November 12, 2003

Yeah but Marcus, you're a genius. That's a different issue. If you leave the project, it's all fall apart.

Reason fan
Wednesday, November 12, 2003

The for loop -- the first is more complicated, as it raises two questions.

Firstly, why do you use postfix ++ when you don't use the result? Oh sure, the generated code is the same as prefix ++... but there's a result! Why aren't you using it? Was it a typo?

Secondly, why do you say "<"... I take it you expect the loop to perhaps terminate with i being n+1, say? But the loop won't do this on its own. I'd say changing the index variable in a for loop is bad practice, and you should use a while() instead. (Or are you just looping while i doesn't equal n? :)

Insert half smiley here.
Wednesday, November 12, 2003

I'm going go out on limb here and say STL Algorithmns are a complete failure in practice.  With out something like Java's lamda classes, err anonymous inner classes, you lose locality of the code.  I don't want to read code like:

std::for_each(begin, end, doit);

where do it is in another part of the file.

christopher baus (www.baus.net)
Wednesday, November 12, 2003

Amen to that brother Christopher! Hallelujah! Preach it!

Dennis Atkins
Wednesday, November 12, 2003

Greg...

Hmm worrying about the binary representation of classes has been pretty low on my list of requirements. If that was a requirement, I'd probably use good old structs without any member functions.

I'd then define serialize functions globally.  Or at least global to the namespace.  But that namespace lookup thing...  How does that go again?

christopher baus (www.baus.net)
Wednesday, November 12, 2003

With regard to loops, I prefer the first form because it's transparently correct, and was the idiomatic way to write a loop over a fixed number of elements until a bunch of wankers with way too much time on their hands decided they could improve on it.

Christopher, I'm totally with you on for_each abuse, but wouldn't go all the way in writing off STL algorithms. I use sort and the various flavours of find a good deal.

as
Wednesday, November 12, 2003

Locality of the Code.
Locality of the Code.
Locality of the Code.

Yes... Locality of the Code is a good thing.

Dennis Atkins
Wednesday, November 12, 2003

Locality, pshaw. (I feel strongly about this.) Names names names! You don't need locality if you have a good name.

You don't inline "fopen", do you? :)

Insert half smiley here.
Wednesday, November 12, 2003

Whoops... "fopen" is a crap name. Pretend it's "GetSystemTime", or something.

Insert half smiley here.
Wednesday, November 12, 2003

>...Libero for state machines;

Arrrgh. Libero is truely craptacular.

Clutch Cargo
Wednesday, November 12, 2003

STL algorithms a failure?  Just because they're not as elegant (C++ was never an elegant language) as other systems doesn't invalidate their utility.

Just adapt them to your needs.  Most uses of for_each I use for calling a member function, so I wrote a quick adapter to make it fit easier. 

Admittedly, there are areas where I'd love some extension, but it's so damn adaptable that I can extend it myself pretty easily.

And for Alexandrescu's book, it's a great intro of where the langauge can go.  The current syntax is pretty obtuse (never designed for these uses), but the potential for moving some of the work currently done at runtime to compile time is huge.  Current uses for it are limited, but still useful.

Like everything else in C++, like templates, operator overloading, and multiple inheritance, it's up to the programmer to find the right uses.

H. Lally Singh
Wednesday, November 12, 2003

Sometimes I wonder what makes good engineers so damn resistent to change. I think it's the very fact that they are so good at what they do: not only they don't have incentive to learn something new but they don't even want to.

I mean, come on: Joel doesn't like exceptions, you guys hate templates, etc.

Hey, I've got an idea: let's all write in C again!

Ten years ago I was running around preaching OOP to engineers writing C in C++.

Today I run around preaching meta programming to people like you who write C++ from 10 years ago in today's products.

Hello, 2003 calling: the world has evolved!

Why templates and metaprogramming? Well, let's count the reasons.

Compare auto_ptr to new:  I haven't had a memory leak in years.

Compare WTL to MFC. Nuff said.

With Loki's Select I was able to have a single code base for 10 different high-performance hashed containers.

In the end, it's productivity: I was able to write a complete 55kloc product in just a few months. Modular, documented and blazing fast.

Learning is hard, guys. That's why you're loosing your job to outsourcing to India.

Nick
Thursday, November 13, 2003

As I program more C++ I use more templates and more STL, as I do so my code gets shorter, more readable, more reliable and more elegant.

Yes, C++ has flaws which result in some ugly syntax (ptr_fun, mem_fun and mem_fun_ref, anyone?), and most compilers report template error messages very badly (but a half decent programmer can learn to read the messages pretty quickly). But there is no denying for_each, transform and the like provide a richer and hence more meaningful syntax than just using for loops for anything.

Mr Jack
Thursday, November 13, 2003

I sort of agree with Nick here -- I've just spent a year working with "C++" developers who are still chasing memory leaks. You start talking about reference counted smart pointers and they have no idea. It's all just too complex.

So they carry on chasing memory leaks instead of writing sensible code.


On the other hand, and I'm guilty of this sometimes, the C++ world does have people who wander around trying to find problems that need their esoteric templating stuff. It's a sort of game: like doing crosswords or something. You just shouldn't let it get in the way of working...

It's a matter of perspective. Metaprogramming isn't a bad thing, it's just some people can let ANYTHING get out of control.

Katie Lucas
Thursday, November 13, 2003

Nick,

The world has moved on, but not necessarily for the better.

The fact that something was introduced, and even became popular, is definitely no indication that it is any good, or even that it does no harm.

First, there was spaghetti code. Then came structured programming, and within 10 years, new code was relatively structured.

Then came Object Oriented design. At first, it was great, because everyone who started using it had a good grasp of structured procedural code; And they were early adopters, so they were actually weighing benefits and costs.

And then the masses came. And now we have class spaghetti, with no end in sight. Unfortunately, there is no modern day Dijkstra + Boare combination to make an effect with a "goto considered harmful" statement - it's all about marketing, and not about merit (It always is, when there's enough money running around; But it _wasn't_ that way 20 years ago, and it wasn't that bad 10 years ago).

Actually, Dijkstra did say that "Object Oriented Programming is an exceptionally bad idea that could only have originated in California". But it really made no effect (and, truth be told, Simula is credited with being the first OO programming language, and it was developed in Europe).

C++ gives you a lot more rope to hang yourself with than C (and perhaps every other language) ever did. When used by informed professionals, it's a good thing. Unfortunately, it is used by programmers.

Ori Berger
Thursday, November 13, 2003

Oh, and just to be clear, I don't claim Object Oriented programming is a bad idea. Or a good idea. You have to define Object Oriented programming before you can meaningfully discuss it, and since there are many definitions, some compatible with each other, and some incompatible. Everyone tends to believe that their definition (which they usually fail to make explicit or well defined) is "the one true way".

s/Object Oriented programming/god/g , BTW - it's a religious discussion. Define what OO means to you if you want a constructive discussion - e.g., use [ http://www.paulgraham.com/reesoo.html ] to characterize which of the 512 options is your OO. And even _that_ does not make it really well defined, it's a start.

And, a question - has any of you who like metaprogramming ever tried doing it in Lisp (or Scheme or Dylan or Pliant)? If so, please share your experience. Mine is that, C++ metaprogramming is like using a pen to write each one of 20 copies of the paper. Eventually, you get where you want; You work hard to get there, which tends to leave you sentimentally attached. You may even have a philosophy about why it's the right thing to do.

When I have work to do, however, I prefer a photocopier.

Ori Berger
Thursday, November 13, 2003

Ori, I agree with you.

I am on the pragmatic side and I don't much care about religious issues.

For me, metaprogramming is simply a code generator. It saves me from typing/cut and pasting the same code in multiple places while preserving the performance and typesafety I would loose by using dynamic oop techniques.

And c++ is the only language today that gives me that, together with a wealth of libs, modern guis, static typing and performance.

But yes, c++ is HARD. Most of the programs pick a subset they know and stick with it, thus the "what's this new templates/exceptions crap" posts. Other languages are easier to use, have better syntax, but they often got that in exchange for libs, size and performance.

However, I have a bit more have faith in programmers. I think they CAN learn. They don't want to, but they can.

And I doubt there is much marketing when it comes to c++. Java and C# are marketing. C++ always struck me as the pragmatic's choice.

Nick
Thursday, November 13, 2003

"Learning is hard, guys. That's why you're loosing your job to outsourcing to India. "

Oh.  We're losing our jobs because Indians learn better than Americans.

And here I thought it was because they are cheaper.

You really are a genius, aren't you?
Thursday, November 13, 2003

Cheaper at doing the exact same things that you do, yes. Learn a new skill, improve yourself as a programmer, and you may be worth the price difference.

I should know, since I currently employ both offshore code monkeys and highly paid programmers. The latter write general abstractions, data structures, high performance protocols. The former do UIs like maniacs.

Work smarter, not harder.

Nick
Thursday, November 13, 2003

The STL is great in small doses.  Templates are powerful.  But I believe that the real reason C++ will never be accepted as a functional programming language is because the syntax stinks.  It's absolutely abysmal.  Really, almost everything that got added to C++ that wasn't in C has embarassingly bad syntax.

Getting back to the for_each discussion... just compare the following:

for (int i=0; i<v.size(); ++i)
  v[i]+=42;

std::for_each(v.begin(), v.end(), std::bind1st(std::plus<int>, 42));

...riiiiiight.  Which is the guy at the next desk more likely to understand on the first read?  Just because template-based programming has its place, doesn't mean that it should be used everywhere.  And as bad as this example is... the more complicated your loop code gets, the more unreadable the STL algorithm equivilent is.  (And someone else already said this, but pulling out the loop body into a function 200 lines down in the code is NOT a solution.)

I've worked with people who loved STL algorithms so much that they really write loops like this.  I dread every time I hit this kind of code.  This goes back to Christopher Baus' point at the very start of this thread, and Roose's followup.  The primary goal of professional programming is not to have fun, to form little puzzles for yourself.  Your goal is to write solid, maintainable code.  And maintainability requires readability.

But, conceding this, template programming certainly has its place.  The ability to get a std::vector of your Foo objects, or a std::map mapping your Foo objects to your Bar objects, is not some arcane C++ library magic.  Instead, it's the tip of the iceberg of the power of generic programming.

Though template syntax is bad, sometimes it's not so much so.  For instance, our "serializable" concept I mentioned before.    If I see foo.serializeSize(), bar.serializeSize(), or baz.serializeSize(), I know what the code means even if I've never seen foo's, bar's, or baz's type before.  If I have a function templatized on its input parameter t of type T, when I see t.serializeSize() in that code, I know immediately what's going on.

I concede you get the same readability if you implement "serializability" with an interface instead of a concept, but you lose some real power.  With templates, I can write a functions that serialize and deserialize ANY vector of ANY class that models the serializable concept.  You simply couldn't do that with the interface paradigm.  Sure, you could write a function that serialized an array of ISerializable*, but do you really want to store your objects like that?  (And managing the lifespan of pointers is even harder and more error prone if you don't avail yourself of smart pointer templates. :-)

Generic programming really shines when you can combine two or more concepts like this and get something really powerful out of the other end.  "Sequence" is a standard library concept and "serializable" is ours, yet they plug right together, and make a really simple, powerful, readable function.

It's all a game of cost/benefit analysis.  Sometimes the benefits outweigh the hassle of dealing with a clumsy syntax and inept C++ compilers and debuggers.  I love this quote from the C++ Faq Lite: "Software Development Is Decision Making".

Greg Falcon
Thursday, November 13, 2003

"Hey, I've got an idea: let's all write in C again!"

Yes, I agree.

Dennis Atkins
Thursday, November 13, 2003

Why stop there?  In the good old days, they used Cobol.  All this newfangled C stuff is unnecessarily complex.

Greg Falcon
Thursday, November 13, 2003

I'll agree to using C again.

We can even use a garbage collector if you're so concerned with memory leaks.

http://www.hpl.hp.com/personal/Hans_Boehm/gc/


Thursday, November 13, 2003

Complexity is the enemy.

Let me say that again -- complexity is the enemy.

Dennis Atkins
Thursday, November 13, 2003

Cobol bad.

C not perfect, but it appears that it was the sweet spot.

Dennis Atkins
Thursday, November 13, 2003

C is in many aspects more complex than C++. Memory and pointer management, for example, are significantly more dificult than using auto_ptr and vector's.

Nick
Thursday, November 13, 2003

Well there are plenty of generic libraries for C to give you vectors and hashes and what all else that work just fine.

True on the auto_ptr, that's 1 point there.

The complexity in C++ is greater than the complexity in C.

C# on the other hand, I'd say is less complex than either.

And Python is mighty fine indeed.

Dennis Atkins
Thursday, November 13, 2003

What a bunch of reactionaries!

new == bad
academic == bad

Let's all go back to struct( void* method;}; in C -- better yet, let's all start using Fortran again because programming is getting too complicated!...

If you dont like the moderated C++ newsgroup - don't read it...
If you dont approve of your colleague or architect's use of metaprogramming, find and justify a better way of solving the problem -- and if that doesn't work, find a new job -- carpentry hasn't changed much over the centuries. Perhaps there is a place for you in that profession.

Bottom line is that choosing the right tool/methodology for the job is often the most difficult step in software development. There was the same backlash against object-oriented programming 10 years ago, and I am sure that in 10 years time there will be a new methodology to bash.

Malcolm O'Callaghan
Thursday, November 13, 2003

I agree with Dennis but I do think auto_ptr<> is a great thing.

The problem is not with meta programming or generics but rather the C++ implementation of these concepts.  Templates are a huge mess.  Debugging compile time errors with templates can be a nightmare -- if I screw up, I often a compile error in some stl.h file rather in my code.  And not just one error, I've gotten over a 1000 errors which can be fixed by changing one line of code -- like what the hell!  This is not a good thing.

You might think I'm backwards for not trusting templates -- but once bitten twice shy.  Use them were appropriate but don't over use them.

Almost Anonymous
Thursday, November 13, 2003

Yeah let me clarify -- I understand this discussion to be about 'template metaprogramming' in C++ and its offshoots, which seem to produce fully and unnecessarily obfuscated code such as the few examples given.

Traditional Metaprogramming, such as the use of code generators like flex and bison, and many other uses, is very often a fantastic and useful approach that *reduces* maintenence nightmares instead of makes them worse.

Absurd use of the C++ template mechanism is no different than absurd use of the C preprocessor to make your C look like LISP or FORTRAN or BASIC -- a bad idea all around.

Complexity is the enemy.

Dennis Atkins
Thursday, November 13, 2003

Template metaprogramming is very useful for library authors. Boost is example of such library.

"Normal" C++ programmers should not use it, unless they acquired this arcane knowledge.

Just because it is fancied on c.l.c++.m doesn't mean everone needs to jump it.

Pavel
Thursday, November 13, 2003

I would argue that at some level all senior developers are library writers.  If your code is going to be used by others, ie you are publishing an interface to others on your team, you are a library writer. 

christopher baus (www.baus.net)
Thursday, November 13, 2003

Do Boost and STL really use metaprogramming?  I don't think they really do in the sense that you will see in "Modern C++ Design."  -- e.g. creating lists of types like

< T1, < T2, < T3, <T4, < terminator> > > >

or whatever it is.

Some of you arguing for template metaprogramming may be arguing against the wrong thing.  I like abstraction.  I like templates.  I like Lisp style meta programming.  I don't like C++ meta programming.

Roose
Thursday, November 13, 2003

I don't think STL does any metaprogramming in the Lisp sense of the word, but Blitz certainly does: e.g., it turns a vector expression of the form "a = b + 0.5*c + d/2" into something equivalent to

for (i = 0; i < n; ++i)
.  a[i] = b[i] + 0.5*c[i] + d[i]/2;

possibly unrolling the loop entirely if n is known at compile time. It does this by generating a class like
Op<assign,vector,Op<add,Op<add,Op<multiply,0.5,vector>, ..........> that mirrors the parse tree.

Ori Berger
Thursday, November 13, 2003

boost does use a lot of meta programming concepts.  Such as tuple for instance which is really Alexandrescu's typelist.

christopher baus (www.baus.net)
Friday, November 14, 2003

Or the lambda library...  You can barely tell what language you are using when you program for it.

christopher baus (www.baus.net)
Friday, November 14, 2003

cccccccccc

cccccccccccccc
Sunday, April 25, 2004

"Software Development Is Decision Making", I could not said it better. some years ago some guy implanted a very strange thing called OOP in C and then was born C++. It was akward, people said it was not a good thing, some loved, some hated, the language evolved and here we are everyone of us use C++ features, not available in plain C, to some extend.
There are lots of programming languages around that aims that every program in that language will be a good program (java, c#, ...).
C++ it´s not like this. C++ it´s about doing things that cannot be done anywhere else, it´s about trying new things, experimenting with new tecniques, and still being able to write good, solid, comercial quality programs, but that, in C++, is a programmers choice.
That´s why C++ it´s called multi-paradigm language, it is not suposed to be the least commom denominator, but to allow everything and let programmers decide what is good, bad or ugly.
Consider this, how many of you use goto´s? No more than a dozen I supose, and it is there if anyone needs it. Multiple inheritance? it´s there, don´t like it, don´t use it, but if you need it, it´s there.
That´s all about template metaprogramming, it´s new, it´s not possible in other languages, and it can be usefull in some places, it´s not the right tool for every job, but is THE right tool for some jobs. So it´s there, use it, don´t use it, give it a try, love it, hate it, but understand that ten years from now we will all be using the best pratices in template metaprogramming; the syntax, the estructure, and everything else will be evolved, and it will be comfortably for most of us; java, C# and any other "good code only" language will suport it, and cutting edge C++ programmers will be using "Quantum Telephatic Oriented Meta-metaProgramming" trying to find better solutions for old and new problems.
It´s not about avoiding complexity, it will sooner or later catch you, it´s about managing it.

Sérgio Vale e Pace
Wednesday, May 26, 2004

If You don't like metaprogramming: don't do it!
If You don't understand it: don't argue (flame) against it !
Maybe You should try java instead of c++ ;-)

Martin P.
Friday, June 18, 2004

*  Recent Topics

*  Fog Creek Home