If this is your first visit, be sure to
check out the FAQ by clicking the
link above. You may have to register
before you can post: click the register link above to proceed. To start viewing messages,
select the forum that you want to visit from the selection below.

C++0x rationales?

Hello.

I hope I've come to a right forum to have answered my questions. :-)

A few days ago I began to be interested in the upcoming C++0x standard. And I was surprised by some of the committee’s decisions. I'd like to know if there's a place to learn the reasoning behind these decisions - why the committee chose such awkward solutions in some cases instead of going in a more straightforward way. I'm not saying the committee decided wrongly, I'd just like to learn their rationales. In particular:

- Template aliases
Why the committee decided to change syntax to

Code:

template<typename T>
using TypeName = SomeType<3,T>

instead of extending the current typedef

Code:

template<typename T>
typedef SomeType<3,T> TypeName

?
Using different syntax for essentially the same thing complicates the language, especially when semantics of the "using" keyword differs from the semantics of "using" declarations.

- Attributes
Why was chosen the "postfix" notation (i.e. attributes are specified after function, parameter etc. names) instead of the "prefix" notation like in C#. IMO the prefix notation is more readable.
Moreover, one would expect that attributes do not change the semantics of the source code, they should be rather "hints". But some of the proposed attributes do not seem to satisfy this criterion.

- Lambdas
Why the lambda syntax is so cryptic and does not resemble the more straightforward syntax of anonymous functions in other C-like languages? Perhaps inspiration by Haskel?

- Garbage collector
Why there are attempts to introduce the garbage collector at the language level instead of at the library level? When defined at the library level, the garbage collector could already have been part of the standard instead of postponing it repeatedly to later specs. I don't believe that the GC at the language level can be introduced transparently, without breaking the current code. The GC at the library level should work in cooperation with smart pointers (allocated memory would be available for garbage collection if no shared_ptr was pointing to it), like it does in most of the already existing GC implementations.
IMO, the only thing that GC at the library level now needs to specify are the finalizers - the spec must standardize their syntax (perhaps the Microsoft's syntax from managed C++?) and their relation to destructors (e.g. destructors would call the finalizers implicitly, when they are both defined).
(Concerning the ever-occurring question of managing scarce resources, the unique_ptr might be determined for this task, for instance.)

- shared_ptr
This question is related to the previous one: Why is shared_ptr restricted to reference counting implementation? From the point of view of shared_ptr users, the exact garbage collection mechanism is irrelevant. But if shared_ptr is adopted to the standard in the current form, relaxing its implementation from reference counting to other GC mechanisms would be very difficult in the later specs.

> I'd like to know if there's a place to learn the reasoning behind these
> decisions - why the committee chose such awkward solutions in some
> cases instead of going in a more straightforward way. I'm not saying the
> committee decided wrongly, I'd just like to learn their rationales.

First, please feel free to criticize the standards committee. They're doing a great job but they are not gods and sometimes they even make mistakes. That's why the involvement of the C++ community in the standardization process is so important and I always encourage it. If you peruse my columns on Informit (in particular, look at this section which has been covering the C++0x standardization since 2003 http://www.informit.com/guides/conte...lus&seqNum=216 ), you will see that I usually present the criticism and rationale regarding each C++0x feature.
The lambda syntax has been modified three times, and if you look at the previous syntactic notations you'll even be more puzzled. I'm not married to the current notation but I believe it's much better than the original <> notation or the _1, _2 etc notation.

With respect to attributes: the rules are indeed about to change so that the attributes can appear before the function's name. Again, you're welcome to read my latest column about attributes.

With respect to GC: there's been a lot of research and experiments about it. I'm not going to present all the arguments here but suffice it to say that a library-only solution can't work in C++. You also need to address the issue of destructors of objects with static storage duration in a multithreaded environment. You need to address backward compatibility with C and with legacy C++ code and you need to address the new memory model of C++ that's been customized to support multithreading. At the end of the day, the real criticism WRT GC is different: C++0x won't have a GC and that's a real miss (this is the result of the infamous "Kona compromise" which was meant to expedite the standardization process, except that it didn't work -- the standard is already 3 years behind schedule but of course, we won't have a true GC. Now that's something I criticize wholeheartedly!)
With respect to template aliases: typedef works only for complete types so you can't used it with templates, unless you change the rules of the language radically.

Criticizing the committee decisions seems pointless to me until I have enough information and unless I post my suggestions at right places. Otherwise it is like shouting from an open window - its good for venting oneself, at most. :-)

But yes, critique... It seems to me that the basic problem with some of the C++0x features is over-complication. C++0x seems to look for all-solving constructs, instead of suggesting solutions that are really useful. It's no wonder that the spec is behind schedule.

Say the "attributes". Who will ever need to use an attribute with a variable or with a block? I would much more appreciate if attributes could be used only for types (esp. classes) and functions (esp. methods). And that later this feature would be extended towards e.g. aspect oriented programming. Not towards a wider range of C++ entities.
The same over-complications with garbage collection.

Actually, I mentioned the GC in my previous post mainly to be able to formulate my last point concerning shared_ptr. But in fact, I was also reacting to how Bjarne Stroustrup was explaining difficulties with GC at http://www2.research.att.com/~bs/C++0xFAQ.html#gc-abi; similar assertions I've heard elsewhere too. It's plain that B.S. is talking about *ordinary* pointers keeping GC allocated memory alive, not smart pointers.

None the less, I don't see why GC memory kept alive by smart pointers would be a problem for legacy and C code. They don't use smart pointers; they can still allocate memory on free storage (but they are responsible for deallocating it); they can use memory allocated on GC heap (but they are not allowed to release it).

template aliases: The link Vijayan pointed me to contains rather long and complex discussion. It'll take me time to digest it. Then I may change my opinion, but at the first reading I don't agree with many of the arguments.

However, concerning the typedefs: If I were to choose, I would prefer the second model for defining typedef templates (according to the proposal N1406#2.2, i.e. argument matching and deduction), not the first one (i.e. typedef specializations). But I would also keep the "typedef" syntax, not the "using" syntax. The new "using" syntax would be justified when e.g. function templates aliases would be allowed - which is not the case. In the current C++0x state it seems to me that the only justification for the new syntax is that "the name being defined comes up front where we can see it", as B.S. writes.

Take my opinion as an opinion of a man comming from outside. I would bet that most people would consider this new language feature as another language complication to an already complicated language. As I do.

using vs typedef

Most people will prefer something familiar to something new - even if the new is simpler, more general, etc. Also, adding anything at all will add to the total complexity. This makes it tough to make improvements.

What convinced me to prefer using over typedef was that the very experienced programmers on the standards committee repeatedly got confused and made mistakes when trying to extend the typedef problems to handle templates. Those problems completely and almost instantly went away when we introduced the using syntax. For examples, see http://www2.research.att.com/~bs/C++...template-alias

I don't thing the problem is in that the syntax is new. But instead that there already exists something very very similar that seems to be a good candidate for the generalization. And even if you have good reasons to establish the new syntax (e.g. to keep the language internally consistent), it is very hard to see any semantical distinction between the two - from the language user point of view. Esp. when one says to himself: Why didn't they modified the syntax of the templated typedef instead of changing the syntax completely? Even after reading all the documents you (all) pointed me to I still only guess the reasons and still have doubts.

I dare to foresay that people will gradually stop using "typedef" and replace it with "using" even in the non-templated version.

> "We tried with the conventional and convoluted typedef solution,
> but never managed to get a complete and coherent solution until
> we settled on a less obscure syntax."

Very good article, it has answered many of my questions – and raised others :-).

I’ve read most of the suggested literature. I’ll try to sum up my findings and ask several questions:

- Attributes and Lambdas
I’ve done with the answer of Danny that the syntax is still about to change. I still think that the syntax is unnecessarily complicated and that the features are aimed in other direction than they should. See "overcomplication" bellow.

- Futures and promises
Do I read the specification correctly, that a promise can be set only once? Thus it cannot be used for implementing the "yield" keyword, generators etc., can it? What C++0x constructs can be used for this purpose instead?

- Garbage collection ABI
The ABI doesn’t allow for copying collectors (and more importantly hybrid generational collectors). Copying collectors need a closer cooperation between the GC and "smart pointers" (the smart pointers need be updated whenever copying takes place). They also need to prevent copying when the memory is pointed to from derived ordinary pointers. The ABI simply doesn’t provide sufficient means needed to support copying garbage collectors. Also, the ABI probably prevents having several GC working at once (different types of object managed on different GC heaps), though I’m not sure.
(Originally I’ve misread the article and was thinking that it talks on GC with ordinary pointers. Sorry.)

- shared_ptr
As written in my previous post, why the smart pointers are bound to the implementation with reference counting? Why they do not allow transparent change of implementation (in particular a proper GC implementation)?

- constextp
A constexp function is in fact a "pure" function, i.e. without side-effects, only the keyword is misleading :-). What’s the purpose of heaving constexp on the default constructors in many library classes? (It allows construction of e.g. an *empty* shared_ptr object.) Esp., why the constexp is used only for the default constructors?

- "Overcomplication"
Though in general I like the C++0x proposal, in several cases (mostly discussed above) I still feel that the proposal is "overcomplicated".

As an (counter-)example take "constexp". The academician you cite in your article was right when asking "Why don’t you just provide a full compile-time interpreter?" I think that one day this interpreter becomes a reality and requested feature. But you are right too that we don’t need the full interpreter now, because we’re trying to solve a much more modest problem. And this is IMO the correct approach – do not introduce a feature unless you know that you need it.

On the other side, there are e.g. attributes. A very general and complex syntax has been developed. And now we have the syntax, and do not know what to use it for. Very few attributes have been added to the specification, and they still use only a very small part of what the syntax allows. This is what I call the "overcomplication". If we didn’t have such big ambitions, the syntax could have been much simpler and more readable (e.g. similar to the Microsoft’s C++/CLI attributes).

You have raised many interesting points. I'll address only the shared_ptr question. You're essentially asking why shared_ptr must use references counting, right?
The main reason is that shared_ptr is based on a similar class designed for Boost, and that one implemented sharing by means of reference counting. What other models of sharing would you like to see, considering that shared_ptr was designed when C++ didn't officially support multithreading? Recall that you can use the small and efficient unique_ptr class in some cases. Allowing an underlying GC to replace the current shared_ptr model of resource management is quite problematic because GCs aren't deterministic and that means that shared_ptr wouldn't be able to own objects with destructors. This very issue is what makes GC so complex in C++.

> - Attributes and Lambdas
> I’ve done with the answer of Danny that the syntax is still about to change. I still think that the
> syntax is unnecessarily complicated and that the features are aimed in other direction than they
> should. See "overcomplication" bellow.

I don't think that the lambda syntax will change. If we wanted to change it, it would not be obviously
in which direction to do so. IMO the main problem is not syntax but (1) lambdas are not polymorphic
and (2) they will be seriously overused. In most situations, I prefer a named function object - which
doesn't suffer from (1) and can be used in several places. Lambdas can lead to a lot of cut and paste
hacking.

> - Futures and promises
> Do I read the specification correctly, that a promise can be set only once? Thus it cannot be used for
> implementing the "yield" keyword, generators etc., can it? What C++0x constructs can be used for this >

purpose instead?

It can be set only once. I wanted a message queue, but didn't get it. Try a vector of promises.

> - Garbage collection ABI
> The ABI doesn’t allow for copying collectors (and more importantly hybrid generational collectors).
> Copying collectors need a closer cooperation between the GC and "smart pointers" (the smart pointers
> need be updated whenever copying takes place). They also need to prevent copying when the memory is
> pointed to from derived ordinary pointers. The ABI simply doesn’t provide sufficient means needed to
> support copying garbage collectors. Also, the ABI probably prevents having several GC working at once
> (different types of object managed on different GC heaps), though I’m not sure.
> (Originally I’ve misread the article and was thinking that it talks on GC with ordinary pointers.
> Sorry.)

It is claimed that the ABI can be used with a copying collector. I'm not sure; it has not - to my knowledge
been done - and I'm not really interested in copying collectors anyway. At the current state of the art a

copying collector requires twice as much memory as no GC. That's a lot and the GC ABI is not really meant

for programs that litter the world with garbage. It is aimed at collecting what's left after you exhaust

the usual structured techniques based on constructor/destructor pairs.

> - shared_ptr
> As written in my previous post, why the smart pointers are bound to the implementation with reference
> counting? Why they do not allow transparent change of implementation (in particular a proper GC
> implementation)?

They wanted a shared pointer; a pointer to express shared ownership. Not a pointer to garbage collected
memory. Destructors are called when the last owner goes away. For almost all uses, I prefer unique
ownership as represented by unique_ptr.