Concepts Get Voted Off The C++0x Island

On Monday, July 13th the C++ standards committee voted "Concepts" out of consideration for C++0x.

First, skepticism regarding the feasibility and usefulness of concepts intensified the antipathy towards this proposal. Some people expressed concerns about compile-time and runtime overhead. Second, the creators of the Concepts proposal tried desperately to improve and patch Concepts. The last nail in the coffin was Bjarne Stroustrup's paper "Simplifying the Use of Concepts" from June. It's a masterpiece in terms of presenting Concepts but it also scared folks. The general sense was that concepts were broken, the committee was not sure what the correct direction was to fix them, and it would probably take several more years to come up with a reasonable fix that would achieve consensus. Considering that Concepts were originally designed to simplify C++, a critical mass of committee members agreed in July 2009 that it was time to bid Concepts goodbye.

Edit:

For more on the meeting see "The View (or trip report) from the July 2009 C++ Standard Meeting" part 1 and part 2

Edit 2:

Bjarne Stroustrup on The C++0x "Remove Concepts" Decision.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.

Sad day

As one of the co-authors of the Concepts proposal, this was a particularly sad day for me. Here's my personal take on what happened.

While the Concepts proposal was not perfect (can any extension to C++ really ever be perfect?), it would have provided a very usable and helpful extension to the language, an extension that would drastically reduce the infamous error messages that current users of template libraries are plagued with.

It is a real shame that disagreement over what I viewed as an uncontroversial design point turned into the sticking point. The Concepts proposal included two kinds of Concepts, the normal (explicit) Concepts for which concept maps (think instance declarations in Haskell) specify when a class implements a concept, and auto Concepts, for which the compiler generates concept maps automatically.

It would certainly be more convenient for all concepts to be of the "auto" variety, but a major use of concepts in C++ is to provide a means to disambiguate calls to overloaded function templates, and it turns out to be extremely dangerous (silently erroneous behavior) to use auto Concepts with overloading based on template constraints. For example, the vector class has a constructor template that takes an iterator range. If the iterators are Forward Iterators, then the constructor dispatches to an algorithm that figures out the length of the range and then allocates the vector to the appropriate size before filling it in. If the iterators are Input Iterators, then the constructor dispatches to an algorithm that incrementally resizes the vector as the input range is read in. Now, if auto Concepts are used for iterators, then many input iterators will be mistaken for forward iterators, and the dispatch will go to the wrong algorithm, causing the wrong run-time behavior. (Imagine dispatching to the forward iterator algorithm when the iterator is over an iostream.)

Despite this issue, there was strong opposition against explicit Concepts based on fears regarding their usability. It is unfortunate that this fear remained, even though there are two very useful historical precedents that point to explicit concepts being quite usable. First, the present day coding style of using iterator_traits (and other traits for other abstractions) amounts to the same amount of work by programmers to establish that a class implements a concept. Currently, the programmer has to select the appropriate iterator tag for their new iterator class, which corresponds to choosing which Concept to write a concept map for. Second, Haskell has had explicit type classes for several decades now, and I've never heard complaints from Haskell programmers that writing instance declarations is too much extra typing. Especially because instance declarations (and concept maps) can be parameterized.

Despite these historical precedences, there was a effort late in the process to somehow remove explicit concepts and make the auto concepts safer. But it was really too late for such large changes, and since agreement could not be reached, the whole proposal was pulled.

Oh well, it may be a long time before we reach consensus on the design for Concepts, perhaps we never will. But in the mean time we're going to push forward slowly with a production-quality implementation.

Of historical interest.

language-level support

I would think that the existence of cumbersome library-level concept attempts in Boost would have convinced the C++ deciders that language-level support was justified.

It made me smile to see Bjarne argue that dynamic-language-like structurally typed templates are superior to statically-typed-like named concepts, because apparently nobody has ever had any difficult-to-debug template problems, and "discipline and bondage" named types never helped anyone arrive at a more correct program! Here's a novel idea: why not support both?

Bjarne's argument against that is that programmers would be confused if both were supported, because they'd be tempted to use one or the other style exclusively, and confused when they encountered something outside their preferred style. C++ programmers aren't confused already? You already have those people who understand boost concepts-on-templates, and those who don't. Designing for "Joe C++ user" is dumb. The language is already tricky; the only saving grace is that it can be expressive with minimal (run-time) abstraction penalty.

I do have to laugh that the solution to "templates using primitive types require free functions" is to check both member functions and free functions. While that will work, why not just allow in general the definition of free functions that can be called as members of their first argument? Scala treats primitive types as classes and the universe has yet to implode.

More evidence that C++ is a dead-end

I've been actively watching the progress of the committee through it's mailings for the past two years, and was anxiously awaiting additions to the language that would begin to modernize it and address some of its more serious shortcomings. Disappointingly, aside from concurrency, the language is relatively unchanged. We needed template type checking, support for easing integration with other languages, better support for type and method reflection, support for authoring more secure code, rudimentary garbage collection, etc. What we got was C99 and a few other random odds and ends.

It seems like time is passing C++ by. The rest of the language world is progressively moving towards managed or native/managed hybrid languages like java/c#, dynamic or smalltalk derived languages like objective C, python, and ruby, and integration between languages that cover different concerns. C++ is still living in it's own isolated world with its unparsable grammar, inability to integrate with other languages, difficult tooling, and lack of reflection support. And with no effort to even address code security and memory safety, it completely fails to address its role over the past few decades in making systems vulnerable to all sorts of malware via buffer overruns and other exploits.

It becomes harder to justify the costs in complexity, code safety, security, and stability in large software projects with C++ vs. a marginal speed advantage. And with the new standard being only marginally different from the previous one, I think we'll see C++ increasingly be relegated to the lower level performance critical functions of larger projects where its various costs in terms of complexity, instability and security can be kept to a minimum.

My feeling 10 years ago was

My feeling 10 years ago was that any improvements made to C++ can only be very marginal in the current language framework, and C++ is an evolutionary dead end. Today I'm surprised anyone sticks with it, and I'm even more surprised that some companies stick to exclusive C++ interfaces even past its sell-by date.

The future of performance is GLSL/HLSL/Cude/OpenCL, or dynamic code generation from a higher-level language.

Dead ends

While I'll agree that C++ is "an evolutionary dead end", seeing as it has grown to the point where it's collapsing under its own complexity. But I'm not at all surprised that people stick with it. It interfaces well with operating systems, has libraries for everything including graphics and sound, has a high reputation for performance (and benchmarks to back it up), is well-integrated with mainstream IDEs, and doesn't require much runtime support.

That it's unsafe, insecure, subject to memory leaks, relatively inflexible, weakly modular, has awful concurrency properties, requires a deep developer understanding of compiler to achieve performance, has relatively weak support for refactoring, is non-interactive, etc. - these things don't seem to weigh as heavily on your typical 'engineer-turned-programmer'.

They can work around those problems, right? But they can't make Ruby give much better performance, and they can't make Lisp fit into a small embedded footprint. That's the logic followed.

And it hurts, badly, that some features (garbage collection, for example) have a poor reputation for performance... perhaps one that is no longer fully deserved. I've heard arguments from programmers - who were actively frustrated with hunting down memory-leaks in a large C++ program - arguing that they aren't willing to give up even 5% performance to avoid the leaks. They just need to hunt them down. (Same goes for deadlocks, race-conditions, etc.)

Finally, there is the not insignificant incumbency advantage. C++ is what the programmers know, so they write projects in C++. Since projects are written in C++, C++ is what is in-demand by companies. Since C++ is in-demand by companies, programmers learn C++. Since programmers learn C++, C++ is what they know. Platform technologies (like programming languages) are involved in dozens of such virtuous cycles, offering incumbency advantages, and it is difficult to break them... i.e. what is the cost of switching to a new language mid-project? And if you don't switch, what is the cost of starting a new project in a new language that your programmers don't know? Now that you have dozens of libraries, wouldn't you prefer to reuse them?

I can see why companies stick with this particular 'evolutionary dead end'. It isn't easy to shed it, and there is no clear competitor available today that achieves the properties desired. Companies also perform what they call 'future discounting'. The 'proposed' future-advantages of a competitor language aren't worth much.

This virtuous cycle can be defeated at least two ways: (a) technology fads (OO was such a fad, which propelled C++ in the first place), (b) provable order-of-magnitude improvements in some critical area that cannot readily be achieved by tweaking existing languages or by sufficient elbow-grease of engineer-turned-programmer.

The latter doesn't happen easily or often.

The values of your typical LtU'er don't quite match what many Joe engineer-turned-Coders believes important. At least not initially.

I agree with Sean that C++ performance is merely a temporary advantage, that the future for performance will be high-performance code generated from higher-level languages (a sort of meta-programming, possibly some declarative meta-programming) and targeted for select devices (CPU, GPU, FPGA, etc.).

But features for producing HLSL/GLSL/etc. code aren't all readily available right now in a language that has other properties these engineers will desire... and many of them are designed to integrate with C/C++ anyway.

Bling provides abstractions

Bling provides abstractions for generating HLSL code from C# created expression trees (and LINQ for generating unabstracted CLR code from expression trees). Vertigo provides the same for Haskell. As long as you are generating code, it doesn't really matter how fast your language is (to a point), you could totally use Haskell to build an entire high-performance system as long as you avoid running Haskell very often during the critical loops.

C++ is a strange beast, in some fields it has completely died out and has been replaced almost entirely either by managed or scripting languages. Still, in other fields (e.g., games) its still on the ascension over C. It is both a language whose time is coming and whose time has past.

Let's face it, C++ is just a horrible language and its C-predecessor is a much better alternative for programming legacy systems. I don't think many universities are teaching C++ as a first or second language anymore (as was the case when I was in school). Even the typical OS class is still taught in C, so learning C++ becomes more of a dedicated technical task.

The number of good programmers capable or willing to program in C++ is actually decreasing, where "good" has a higher bar in the C++ world as learning C++ best practices requires a lot of on-the-job experience. This leads to high demand for C++ programmers, as its very difficult to train up other programmers to be proficient in C++. I tried working for a C++ shop for a few months and realized it would take me awhile to become proficient, which is disheartening as a language designer. The people who were proficient had a lot of C++ exclusive knowledge/experience that is hardly transferrable to other languages! For this reason, if you have no reason to use C++ in your project you probably shouldn't, as finding qualified programmers is going to be a pain!

Who's heard of 'Bling'?

Who's heard of 'Bling'? Look up 'Bling, programming language' on Google and you get hits for 'programming languages for blind people'. A language nobody has heard about doesn't even count as a contender.

And, yeah, Conal has done some cool stuff with functional reactive programming and functional graphics (including Vertigo). I'd really, really love to see Haskell or a mostly functional graphics programs start kicking benchmarks around and proving that they can walk the walk on a large scale. That sort of show at E3 or elsewhere would catch attention.

I'm stuck with C++ at work because I can't demonstrate that other technologies can do better, and I'm certainly not going to be paid to produce such demonstrations myself. (I have, however, put effort into extending support for integrating other languages.)

Whether C++ is a horrible language compared to C or not is an open question. I'm rather fond of certain C++ features missing in C, especially: exceptions, abstract base classes for modules and portability, smart-pointers, template-based structural typed programs, template-based adapters and typeclasses, template expression constructs (which may be used for dataflow, inlining complex computations, etc.), namespaces, destructors on stack scope.

C++ is a horrible language, no doubt. Scala and D are much better for similar feature-sets, though they aren't quite competing for performance with C++ yet. But between C++ vs. C, I'd choose C++ every time. I simply won't use the features that bug me (like inheritance from concrete classes).

These are all side projects

These are all side projects that show proof of concept. Bling is my version of Vertigo for C#, its not a programming language as it is embedded in C#. If someone wants to go and produce a language or support shader code generation in a higher level language, its definitely doable (ok, and there is always Sh for C++ if you must).

What C lacks in features it gains in simplicity. Its fairly easy to wrap my mind around what C is, whereas I have no hope of comprehending C++ because I'm not a super genius. Any larger abstractions I needed in C I could hack up (even without macros); e.g., v-tables for object oriented programming or arenas for automatic memory management (this is how I implemented most of Kimera). C++ is a step backwards in terms of simplicity, although the features can be addicting you have to essentially carve out our own language out of a mess of features.

What C lacks in features it

What C lacks in features it gains in simplicity. Its fairly easy to wrap my mind around what C is, whereas I have no hope of comprehending C++ because I'm not a super genius.

This is exactly where I am too. Most of the best or most promising code generators are written in C++, ie. LLVM and nanojit, but I'm very hesitant to jump into C++ waters. It just has significant conceptual hurdles that must be surmounted to achieve even a base level of productivity. Needs must though.

Incremental

I tend to follow the 'learn a language incrementally' method, and favor a language that simultaneously allows me to be productive with just a little knowledge and allows me to grow. This is as opposed to a language that requires, or allows, me to wrap my head around it and grok all I can easily abstract and compose from the very start. I'm no super-genius. I don't need to be one. Being persistent and willing to learn is sufficient. I suspect it'd work for you, too.

C++ allows productivity without requiring knowledge of all its advanced features. Indeed, if you wished it you could be productive with just the subset of C++ that corresponds to a small subset of C, then advance from there.

Of course, C and C++ both have 'gotchas'. Gotchas are very bad. They're violations of abstraction, prediction, or composition. They're exceptions to rules. A change to a C or C++ program can break the whole program. Composing two correct C or C++ programs can break both of them, especially if concurrency or interprocess communications or inversion-of-control or memory management is involved. 'Plain old data' types are 'special' and other types are forbidden entry in unions. Abstract base-classes are 'special' and cannot be placed on the stack and require lots of extra explicit memory management. 'void' is special and can't be treated as an empty structure. Zero-length arrays are 'special' and require template specialization. Syntactic gotchas include the template-inside-template requirement to avoid 'x<y<z>>' in favor of 'x<y<z> >'. Iterator invalidation rules are a rather painful and seemingly arbitrary set of gotchas.

Gotchas gonna getcha even if you think you have your mind wrapped around the language. C has fewer features, and fewer gotchas, than C++. I suspect fewer gotchas, more than fewer features, contributes to its perceived simplicity.

C is more simple than C++,

C is more simple than C++, there isn't much debate about that. Of course, simplicity is a double edge sword, and features can be nice when done right. Its just that they've been done wrong in C++.

Now, you are right that a programmer working alone only needs to know a subset of C++ to be reasonably productive. But my experience is that things get harry when there are other users of C++ around you with differing understandings of how to use C++ productively. You aren't just stuck working with your code, you are stuck working with someone else, and...you basically have to be at the same level they are or working together will be very difficult. Then what happens is that each team/company dictates their own C++, which is generally dictated by the C++ whiz on your team.

Scala has a similar learning curve to C++, it has a lot of features that take awhile to wrap your head around (traits, existential types, implicits, pattern matching, ...). But the thing is...it is just a better design than C++ so there are fewer rough corners, more consistency, and the productivity tricks in Scala are less likely to shoot yourself in the foot. Of course, Scala is managed, but thats the future anyways, there are no new and significant purely compiled languages on the horizon.

Nice when done right

features can be nice when done right. Its just that they've been done wrong in C++

They could have been done better, for sure. As you say, they were done better in Scala. But to say "they've been done wrong" says something different than "they've not been done optimally". I can only think of a few cases where C++ would have been better off to not bother with a feature.

when there are other users of C++ around you [...] you basically have to be at the same level they are or working together will be very difficult

While I agree, I don't believe this is a C vs. C++ issue.

The same would apply to understanding the "tricks" used by a C whiz - the sort who can build a quick framework, pools of worker threads, a micro-managed arena with garbage collection, structures of function pointers, etc. It isn't as though your typical C programmer could dive into Mozilla's SpiderMonkey code (C implementation of JavaScript).

If the language provides first-class support for a feature, you at least can learn to recognize it and understand it at the language level rather than needing to recognize a design pattern scattered across many functions and possibly across many files. Further, there is likely to be less semantic 'noise' around a pattern... e.g. for error-handling, then cleanup after an error, and so on.

Hmmm

Well, but it is a little bit a C/C++ issue... in as much as it is still possible to write your C so that it is easy to read -- even if you are doing fancy tricks, you can provide safe abstractions and clear code defining them.

Whereas it is not really possible to write your C++ code in a way which escapes the language it is written in. The best you can do is try to find and agree on one sane subset. But that is fettering everyone to one "level"...

Basically I think coping with people writing at a more "tricky" level is a little bit easier in a language you are the master of than in a language of which you are not...

The semantic noise around a pattern is an interesting point. But the trouble with C++ is that the semantic noise around the definition of a feature tends to be quite high as well. The C++ Programming Language is neither short nor pithy.

escaping the language

Whereas it is not really possible to write your C++ code in a way which escapes the language it is written in

How much Boost have you used? ^_^

it is still possible to write your C so that it is easy to read -- even if you are doing fancy tricks, you can provide safe abstractions and clear code defining them

I'm not convinced that C makes this possibility any easier than C++. One must weigh the common use of preprocessor macros for these 'fancy tricks' against C++ having an ugly syntax to start with, and one must weigh against its initial ugliness the syntactic advantage C++ achieves for safe abstractions using template expression builders and functor objects.

C++ in general seems to have the safety advantage. C programs of any complexity often require typecasting, indexing arrays past the end of a structure, and other tricks of questionable safety.

Basically I think coping with people writing at a more "tricky" level is a little bit easier in a language you are the master of than in a language of which you are not...

While it may be easier to deal with 'tricks' when you're a master, it'll also be easier to deal with them if the 'tricks' are shallow, which is easier if the language has more features with which to build tricks. I suspect those two aspects tend to balance out for learning purposes, but first-class support for features is often easier to optimize and analyze for safety and such than features built from deep 'tricks'.

A simple language whose rules can be fully 'learned' very easily will tend to require "tricky" level coding to achieve anything of value. And mastery of any language will probably involve learning a few tricks to work around known limitations of the language.

the trouble with C++ is that the semantic noise around the definition of a feature tends to be quite high as well. The C++ Programming Language is neither short nor pithy

That much I'll agree with wholeheartedly. C++ abstractions tend to overlap and combine concerns far too often (e.g. classes vs. namespaces, templates vs. functions, struct vs. class), and have way too many gotchas (e.g. awful support for unions, strange defaults like non-virtual destructors if another operation is virtual, integers of implementation-defined size). The problem is exacerbated by some pointless designs for the standard libraries (i.e. inability to use character-traits classes for strings for any viable purpose).

I don't think C++'s size or complexity unreasonable, but I believe one could have achieved a great many more features with the same size and complexity.

Being persistent and willing

Being persistent and willing to learn is sufficient. I suspect it'd work for you, too.

Sure, I only meant that a good C++ project likely uses some of C++'s more sophisticated features, so your uphill battle to "productivity" is just that much steeper.

Please clarify

Why would the "uphill battle to 'productivity' be that much steeper" as a consequence of leveraging sophisticated features?

Are you implying that not using sophisticated features will save you time or effort? If so, evidence to the contrary seems overwhelming. There is a reason that unsophisticated languages are often known as 'Turing Tarpits'.

The most common challenge I've seen in beginning C++ users for large projects has always been inversion of control and concurrency... and that has nothing to do with C++ in particular and far more to do with the project being a large one. C++'s primary impact on the subject regards memory management (particularly: avoiding stack explosions via asynchronous queue-based operation, and destruction of the callee) and memory safety (locks, avoiding deadlock), and it isn't as though C does any better on these subjects.

For large projects, users have examples to follow on how to use sophisticated features. For small projects or independent plugins to a larger project, they have little need to use sophisticated features.

What situation are you imagining where a beginning user must grok a great deal of C++ before becoming as "productive" a contributor as a C programmer would have been?

I'm not sure how to explain,

I'm not sure how to explain, since it seems pretty obvious to me. Assume a newcomer to C++ from a C and C#/Java background, who when approaching C++ must at the very least deal with a few new concepts, namely syntax for declaring classes, multiple inheritance and its attendant problems, namespacing, and new/delete. These would seem to be the bare minimum required for working with any C++ codebase, and I don't mean you need to know how to implement these things, I mean you need to understand how they influence the semantics of objects you use.

C++ of course has far more sophisticated and insidious constructs as well that are used pervasively in larger projects, so one must also become familiar with operator overloading, templates, and idiomatic C++ concepts like iterators and RAII before becoming productive when interfacing with these codebases. Does that answer your question?

One-dimensional analysis

A naive analysis would suggest that the need to learn RAII and exceptions hurting productivity rather than helping it, on the basis that they are extra concepts to learn. A thorough analysis must take into account the relative challenge of learning concepts to achieve the same ends without exceptions or RAII.

Consider your newcomer N to some project P. Project P is largely developed in C++ code. The relevant question isn't "how long does it take for N to become productive in C++". More relevant is "how long does it take for N to become productive in project P". Even more relevant is "and how does that compare to the case where P were largely developed in some other language L?"

Concepts that might exist in P include such things as construction, cleanup, inversion of control, dependency injection, concurrency, task queues, worker threads, codecs (compression), cyphers (encryption), filters, mathematical formulae, command sequences, iteration, etc.

A concept will exist in project P's codebase for one of a few cases:

  1. the concept is useful
  2. the concept was not useful, but necessary (required by language). Call this semantic noise.
  3. the concept was neither useful nor necessary, but was included anyway. Call this premature (as in premature abstraction, generalization, or optimization).
  4. the concept is a work-around for the inability to express a useful concept more directly. Call this indirect.

I posit the following:

  1. If the concept is useful, then it - or some related equivalent - would likely be part of the codebase if P were written in another language.
  2. the concept was semantic noise, then it is unlikely to exist in the codebase if P were written in a language that doesn't require it
  3. if the concept is premature, then (absent evidence) we have no reason to believe the same or a similar concept wouldn't have been added by the same programmer writing P in another language.
  4. if the concept is indirect, then understanding the program P requires understanding at least two concepts: the indirection and the concept being indirected. The indirection qualifies as another form of semantic noise, and would be unlikely to exist in the codebase if P were written in a language that doesn't require the indirection.

Further, I posit that: if a concept in a project is not given support from the language proper, it will instead be given support via an abstraction or API. An API is essentially a language extension another programmer designed or evolved, complete with its own gotchas, idioms, protocols, and concepts.

Whether it be via an API or language primitive, concepts are given syntactic expression. This expression may possibly be in some consistent form (i.e. functions, or FORTH words - may be consistent even for primitive concepts), or might have a dedicated bit of syntax (i.e. parsing expression grammars, micro-languages like regular-expressions or scripting languages, macros). I have yet to see convincing evidence or arguments that learning one or the other of these is more 'difficult' than the other.

The only concepts worthy of complaint are those that exist as semantic noise or indirections to other concepts. Examples:

  • explicit 'delete' of objects is likely semantic noise relative to the project P to return storage to the system. It might not exist in a language with pervasive garbage collection.
  • use of a task-queue and task functor objects is an indirection for concurrency issues. It might not exist if P were written in a language with deep concurrency support.

If a C++ concept is useful for P, then P' written in C would have included at least an corresponding concept to learn, with a corresponding syntactic form. It may additionally require expressing an indirection that did not exist in C++ due to the more direct support in C++ for C++ concepts. We lack evidence to say which of these two concepts and syntactic forms would be more difficult to learn.

One question, then, is: "which concepts in P written in C++ are semantic noise or indirection that would have no corresponding noise or concept for P' written in C?". Another question is: "what new indirections and semantic-noise would need be introduced for P' written in C?".

For the latter I can say: any equivalent of exception handling and RAII-destructors, if useful, would likely become extra concepts and complexity for P' written in C. RAII and exceptions are complexity reducers. They reduce what one must learn before becoming productive when interfacing with project P codebase, at least relative to C.

I think you're making this

I think you're making this more complicated than it need be.

The relevant question isn't "how long does it take for N to become productive in C++". More relevant is "how long does it take for N to become productive in project P".

I agree. Of course, not knowing a priori which C++ idioms or concepts a project actually uses forces you to become familiar with a sufficiently large subset of C++ so that you at least recognize what you're looking at.

I'm not arguing that all of the C++ features exploited by these projects aren't useful or weren't good decisions, just that the permutations of features a C++ project might employ is daunting to C++ newbies. An extra point for code generators, I'm not sure I would even want more than a simple procedural interface for native code generation.

One question, then, is: "which concepts in P written in C++ are semantic noise or indirection that would have no corresponding noise or concept for P' written in C?". Another question is: "what new indirections and semantic-noise would need be introduced for P' written in C?".

No doubt extra complexity would be required. Or the library would simply punt those features to clients. For instance, LLVM and nanojit both provide code buffer management, where GNU Lightning requires clients to handle it. No doubt that's terrible for high-level libraries, for code generation I'm not sure that's such a bad choice.

No simpler than possible

Not knowing a priori which C++ idioms or concepts a project actually uses forces you to become familiar with a sufficiently large subset of C++ so that you at least recognize what you're looking at.

Disagree. That's just as valid as saying you're forced to become familiar with all the potential concepts used by all the libraries in a project, so that you can recognize what you're looking at. Which is to say: your assertion is invalid.

It isn't a bad idea to learn concepts, design patterns, syntax, API, and primitive features ahead of seeing them and needing to look them up. Knowing them ahead of time lets you use existing features without first reinventing them. But you're hardly forced to do so.

the permutations of features a C++ project might employ is daunting to C++ newbies

Again, that's one-dimensional. C newbies will find the indirections and relative complexity to handle missing features (such as error handling and generics) just as daunting.

Even permutations for a simple language like untyped lambda calculus can easily become daunting. See how easy it is for untyped lambda calculus newbies to wrap their heads around Church numbers, boolean logic, and linked lists. Give them a moderately complex task - such as applying quick-sort or merge-sort to a list or solving Tower of Hanoi - then watch their poor minds boggle at the amorphous uniformity and 'simplicity' of the language.

Semantic indirection, especially when layered heavily, can become very daunting.

Of course, whether the 'complex' language does better depends on whether it supports distinctions useful to the project (in either of the problem or solution domains).

I think you're making this more complicated than it need be.

And I felt you were overlooking essential complexity.

Simplistic analyses for simplicity, especially when you're inferring relative productivity curves and such from them, are an unnecessary and undesirable disservice. Someone designing a language, or picking one for a project, ought to take a broader perspective.

uh, what?

"Abstract base classes are 'special' and cannot be placed on the stack and require lots of explicit memory management."

I don't think you know what you're talking about here. Abstract base classes can't be allocated *ever*. They're called abstract because they're not complete -- chunks of them are missing (they have unimplemented functions). Java is garbage collected and has abstract base classes.

"Zero-length arrays are 'special' and require template specialization."

It's possible you're trying to say something sensible here, but it's lost by the fact that arrays have nothing to do with template specialization, so you'll have to go into more detail.

"'void' is special and can't be treated as an empty structure."

I'm not sure what you mean. Are you objecting that you can't take sizeof(void)?

"Syntactic gotchas include the template-inside-template requirement to avoid 'x' in favor of 'x >'."

That's one of the things C++0x fixes ;)

Abstract base classes can't

Abstract base classes can't be allocated *ever*.

Sure they can be allocated. Every instance of an ABC is an allocated ABC, is it not? It's also a concrete class, of course, but those are not mutually exclusive.

It would be very convenient if one could allocate such things on the stack... as in: declare a variable on the stack to be an abstract base class, then assign to it the result of a factory method that chooses from among a large pool of possible concrete implementations.

In C++, abstract types are very much second-class. They get lower performance memory, require extra idioms for memory management, you can't write copy-constructors and assignment operators for them, and so on.

Boost and some other libraries have work-arounds... e.g. provide a block of memory (using new(location) Object) for local allocation, and only switch to pointers if too large for the block. These work well, but they're hacked in, especially when dealing with alignment issues.

"Zero-length arrays are 'special' and require template specialization."

Arrays have nothing to do with template specialization, so you'll have to go into more detail.

You can't have a zero-length array, but zero-size comes up often in computed compile-time constants. One of the most practical ways to get around this is with template specializations, which allow you to remove the array for size zero (using some form of template<T,size_t n> fixed_size_array { T data[n]; }; template<T> fixed_size_array<T,0> {};)

It's extra pain introduced by an arbitrary and pointless restriction.

"'void' is special and can't be treated as an empty structure."

I'm not sure what you mean. Are you objecting that you can't take sizeof(void)?

No, I'm objecting to the fact that you can't pass 'void' as an argument to a function (unless it's the only argument), or have a 'void' variable that might be something else in a different situation. Both of these foil a bunch of useful template generalizations.

Besides, empty structure and void express the same concept (emptiness, lack of data) and so should have the same semantics.

The DLR is written in C# and

The DLR is written in C# and is pretty accessible. They don't generate to native code though, instead you depend on the CLR for that, but its still useful enough to explore dynamic languages or run-time optimizations.

While none of the remaining

While none of the remaining C++0x features have the sheer complexity of concepts, they will probably have more impact on the typical user. Lambdas alone will be enormously important to std algorithm usage.

The other things you mention, safety, reflection, GC, I think C++/CLI shows what is needed to achieve that in C++, and the result is quite simply another language bolted onto the side. To get the benefit you'd then have to switch off the unsafe things, and you'd have the /clr:pure variant of C++/CLI. And then you'd completely lose all backward compatibility, so why bother?

Just pick a modern language that has rich auto-generated bindings to C++, write new code in that and gradually shrink your C++ codebase out of existence, rewriting a chunk of it whenever it makes economic sense... though that's probably what you're already doing, or planning, like I suspect the bulk of C++ users (it's what I started doing a couple of years back).

Not everything

Basic support for garbage collection, reflection, and rudimentary support for memory safety constructs are not really equivalent to C++/CLI in my opinion. These were implemented in C++/CLI in such a way as to integrate C++ and the CLI runtime.

The point was that "outside" of managed integration, these features are really needed for a large majority of software that still has to be written in C++. For example, the difficulty in exposing C++ classes and structures in other languages is a major issue. Likewise the lack of any sort of rudimentary checking for type or memory safety. These features hardly need to go as far as a re-implementation of a managed environment, even rudimentary support for strictly checked code regions or optional extensions to RTTI would cover these needs.

As far as rewriting code, there is still no alternative to C/C++ for hard realtime, os or system level programming. Even though the barrier to using managed code in games or embedded environments with open source projects like mono is getting low enough to make it a strong possibility, there are still major issues with memory management and garbage collection latency that make them not-quite-ready-for-prime time.

Design by committee = paralysis?

Between this fairly crushing blow to the scope of C++0x, and the rejection of closures from Java 7, it really looks to me as though standardized, committee-designed languages seem to reach a paralysis point beyond which consensus on big changes becomes unattainable. Given how much inertia there is around large code bases, and given the impossibility of objectively assessing how a whole community of programmers will react to any given change, the result is timidity and veto paralysis.

This isn't a problem necessarily, now that there is so much more focus on language interop (viz. Scala running on the JVM, JRuby, etc.), and given that multicore is such a game-changer. Still, it's quite thought-provoking. Seems the benevolent dictator model (Python 3, C# 4) probably supports language evolution better than the committee model (Java 7, C++0x).

I think the best way to

I think the best way to design a language is through a "benevolent language designer/dictator." They might work with other people in the design of the language, but they reserve all the final decisions for themselves and make them quickly. They are also capable of significantly changing the language every 6 months, which might upset those that crave stability, but helps move the language forward and prevents stagnation. On the other hand, the committee model promotes stability to an extreme point.

I'm not sure I would classify C# or any of the other commercial languages as dictator languages: they have to contend with multiple stake holders that are potentially untrained as language designers. All of the innovative languages (e.g., Scala) are going to come from the dictator model, and we can only hope they innovate enough to be picked up by a commercial entity, whereas they stop evolving quickly but get better tool support/documentation/etc...(as is happening with Python, to some extent Ruby).

Between this fairly crushing

Between this fairly crushing blow to the scope of C++0x, and the rejection of closures from Java 7, it really looks to me as though standardized, committee-designed languages seem to reach a paralysis point beyond which consensus on big changes becomes unattainable.

The problem with that reasoning is that lambda-style closures were voted into C++0x, but not Java 7. It's not that concensus is unattainable, but that a committee tries to represent the target audience for the language, and so will accept or reject features on that basis. Java is the red neck language... I'm sorry, I mean blue collar language (see http://www2.computer.org/portal/web/csdl/doi/10.1109/2.587548) and so they do not like closures, but perhaps they would happily vote grits into the language.

If Java were a redneck language

it would be called "coffee". :)

If you think C++ might just be a red-neck language...

Joe.

It's not clear that leaving

It's not clear that leaving concepts out of C++0x was the wrong decision. Even Bjarne, who clearly had an strong (maybe emotional) attachment to concepts ultimately decided against including them. If you look closely, they really did have some subtle issues.

I use C++ constantly; I'm certainly not its biggest fan, but I don't think I could look through the committee's history and point to a boneheaded move or a clear missed opportunity at any point.

The design by committee seems like a bad approach at first glance, but maybe a large expert committee with distributed veto powers is the only way to make sure a language never makes a wrong step. Paralysis beats broken features...

Implicit concept maps

Bjarne's primary opposition to the concepts design at this point was the requirement that concept maps must be provided for "explicit" concepts. He felt that this was overly burdensome and that concept maps should be implicitly determined by the compiler.

On the side of typeclasses, Haskell chose to require that instance declarations be provided explicitly by the programmer in all cases. Do Haskell programmers find this to be either overly burdensome, complex, or confusing? What advantages are there to requiring explicit instance declarations?

Well...

... this is arguably bad design, but you can have a typeclass with multiple "reasonable" instance declarations, and then use newtype(?) and different instance declarations to have two versions of the "same" type, each being an "instance" of typeclass X in a different way.

(A good example of this is escaping me. As I say, I'm not sure it's massively good design practice... Although, eg, I suppose you might do it in a vector library for different norms or something similar.)

Possibly there is someone hanging out on LtU who can tell me what good design in Haskell would be?

As an aside, reading through Soustrup's paper... I can't say the "problems" he finds with explicit concepts have ever caused me problems in Haskell. If I want to be able to "cerr" something I simply require it to be in class Show. It's never really been an imposition for me. After all, if I'm printing it, I know how to show it, so making it an instance of Show is pretty easy. (Can even use "deriving" for my own types.)

But perhaps I am missing his point here? A large part of the argumentation seems to be to do with the intuitions of someone new to the idea/"Joe Coder"...

(What *has* been an imposition has been that, if I want to avoid unsafePerformIO, I then have to rewrite *everything* in a monad to be able to putS... even though I don't care about serialisation order... That and I still can't do space reasoning about a lazy language -- this is probably a failure of my brain however ;-) )

Monoid

... this is arguably bad design, but you can have a typeclass with multiple "reasonable" instance declarations, and then use newtype(?)...

(A good example of this is escaping me...)

The classic example of this making numbers instances of Monoid. Both of these would make sense

instance Num a => Monoid a where
    x `mappend` = x + y
    mempty = 0

instance Num a => Monoid a where
    x `mappend` y = x * y
    mempty = 1

But it's verboten to have both definitions in scope at once. So instead the Data.Monoid library uses newtypes Sum and Product

newtype Sum a
newtype Product a

And declares monoid instances for them

instance Num a => Monoid (Sum a) where
        mempty = Sum 0
        Sum x `mappend` Sum y = Sum (x + y)

instance Num a => Monoid (Product a) where
        mempty = Product 1
        Product x `mappend` Product y = Product (x * y)

More.