A blog about in-depth analysis and understanding of programming, with a focus on C++.

Tuesday, July 21, 2009

Concepts, C++0x, and the Limit

By now, I'm sure you have heard the news. The C++ Working Group has decided to remove Concepts from the C++0x specification. The first question you might have is this: what are Concepts?

This is pretty simple, and I'll explain by way of analogy. Object Oriented Programming (class hierarchies with virtual functions) and Generic Programming (templates) are the prime language-based C++ mechanisms for allowing old code to call new code. Having new code use old code is easy; you call a function. Having old code call code that has not yet been written is harder.

OOP does this by using prototypes, explicit constructors/destructors and function pointers (under the hood, of course). You provide a prototype (your class), explicitly defining which functions can and cannot be changed in derived classes. Under the hood, the compiler creates a small struct for each class that contains the virtual table: the list of function pointers that this class uses. When you derive a new class and override other virtual functions, the struct that your new class uses is created by copying the base class and then overwriting any overridden function pointers.

The important part of this is that it is all very explicit. Everything is directly spelled out for both the compiler and the user. B derives from A, so anything that takes a pointer/reference to A can also take B. End of story.

Generic programming is very implicit. A templated function imposes some requirement on the type of the object that it uses. However, that requirement is never spelled out anywhere. It is defined entirely based on how that template function, and any template functions it calls, uses the type. This interface doesn't even have to be on the type itself; a template can do odd things like require that there be a free function that will take this type as a parameter. If the type you pass in has the appropriate interface, then everyone's happy. If it doesn't, you get a compiler error.

Now, the problem with this is that compiler error. Somewhere deep in the bowels of some template function, something tries to make a call or perform an operation that the type can't handle. That, according to C++, is where the error happened. The compiler now must unroll the call hierarchy, listing each and every function, template type, and so forth along the way, until they reach the cite where the error really happened: the place where you instantiated the template.

Also, the error message itself is usually not something as clear as "the type X you used in template Y needs operation Z that is not available on that type." It is usually obtuse and involves a lot of text about innumerable template types and expansions. It even exposes some of the implementation details of the template you may be using.

Concepts are a proposed way to create a functioning, explicit interface for template parameters. If you're using a template that has a Concept, attempting to use a type that does not conform to the Concept will cause an immediate compiler error, with a reasonable error message explaining that the type doesn't conform to the Concept. It also helps in writing templates. Before, you could do anything with a template parameter and the code had to assume it'd work until someone tried to instantiate it. If a parameter has a Concept constraint on it, then attempting to perform an operation not listed in that Concept will provoke a compile error in the template itself.

All in all, it sounds like a great contract idea. And it had been built up as one of the major selling features of C++0x.

So why'd they ditch it?

Well, first of all, it just wasn't maturing. ConceptGCC, an implementation of some of the older forms of the various Concepts proposals, stopped being developed. The C++ working group has no resources to spend on keeping this thing fairly up and running.

The Standard Library was also becoming a problem with Concepts. Obviously, the library would have been Conceptualized, but there was no clear idea on how to do that. And without a functioning implementation, all the C++ WG could do was argue about one way vs. another.

And all of this non-advancement started mucking up C++0x's schedule. The 0x is supposed to mean pre-2010. Faced with the possibility of C++0x being dramatically delaid (numbers like 2012-2013 were being thrown around), the WG decided to chuck the dead-weight and leave Concepts for the next standard.

Was this the right choice?

Absolutely! And here's why.

Despite all of the hype around Concepts, we are talking about a feature that exists primarily to make error messages nicer. The Concepts proposal does have some interesting adaptation features, that allows one to write a mapping object from an explicit type to a Concept. This object would be used, thus allowing you to make something that doesn't entirely conform to a Concept to do so. But even that isn't such a huge feature, as you can write wrapper classes to get similar functionality (though you must then explicitly use them).

For me, it comes down to day-to-day utility. Concepts won't make my code faster; r-value references will. Concepts won't make using standard algorithms that much easier; lambdas will. Concepts won't decrease the number of times I have to type out a typename; "auto" (like "var" in C#) will. And so on.

C++0x is a lot more than just Concepts. C++0x has productivity enhancements (auto, lambda), performance enhancements (r-value references), and just flat out cool stuff (uniform initialization, user-defined literals). Delaying all of that stuff just to get nicer error messages for template misuse would have been a crime.

I wish C++0x could have had Concepts in it. But getting the rest of the stuff out to the public is far, far more important.

A tip of the hat to the C++ Working Group on sacrificing one of their sacred cows for the betterment of the rest. We'll see Concepts come back for the next standard.

No comments: