Why C++/CLI Supports both Templates for CLI Types and the CLI Generic Mechanism


I’ve been recently puzzling out a strategy for presenting the two mechanisms supporting parameterized types available to the C++/CLI  programmer: she can use either the template mechanism adapted for use with CLI types, or the CLI generic mechanism. This is not unique to the support of parameterized types, of course, but it seems a lightening rod for pernicky questions:


(1) isn’t the support for two mechanisms that are similar in intent but which differ in both gross and subtle semantic behavior confusing for a user?


(2) doesn’t the dual nature of these constructs increase the liklihood of programmer error?


(3) isn’t this a canonical illustration of the undisciplined nature of the C++ language design where everything but the kitchen sink seems to get thrown in? you guys just can’t get your act together, can you?


Let’s see what kind of answers I can offer. [Disclaimer: of course, these are my thoughts, and do not represent either the corporate views or policies of Microsoft.]


The C++ binding to the CLI which I refer to as C++/CLI represents an integration of two separate object models: the static object model of native C++ and the dynamic program model of the CLI. We’ve seen conflicts between these two models before – in particular between the native and CLI enum, the native and CLI array, and the native and CLI reference class.


Under the CLI object model, individual languages are – not to put too fine a point on it – somewhat diminished – much as how in a modern country, the individual states while soverign are constrained to the laws of the central authority. For example, the CLI defines the underlying type system within which a language operates, as well as the inheritance model. As we saw in an earlier blog, the CLI does not, for example, support private inheritance, value inheritance (that is, the inheritance of implementation but not of type), or multiple inheritance (MI). While a language can choose to support these aspects of inheritance, that support requires a mapping onto the existing CLI object model because there is no direct support.


The Eiffel language under CLI, for example, choose to provide an MI mapping because it felt that (a) MI is a valuable inheritance model, and (b) its users would be dimished without its support under the CLI – that is, it would give its users a dimished programming experience under the CLI than on native platforms, and this would likely deter them from migrating to the CLI itself – or at least of migrating to the CLI while continuing to exercise their Eiffel expertise and culture.


We did not feel the same imperative as Eiffel with regards to multiple inheritance. But we did feel that imperative towards deterministic finalization of reference types declared within a local block, and so we provided a mapping of a class destructor to a IDisposable::Dispose() method, which is the CLI pattern of reclaiming resources prior to garbage collection finalization. Similarly, we did feel the imperative towards automatic memberwise copy and initialization – as supported by a copy assignment operator and copy constructor – and so we provided a mapping. (But these mappings are constrained by the underlying CLI implementation. We could not map memberwise copy into a value class because we could not guarantee that it could be carried out in all circumstances – at least that is my understanding. I haven’t myself verified that but taken it on faith.)


Again, we did this because without these mappings of essential aspects of the native C++ programming experience, we believe the C++ user would have a diminished experience of programming under the CLI than on the native platform, and that this would deter them from migrating to the CLI – or at least of migrating to the CLI while continuing to exercise their C++ expertise and culture. The template mechanism is another of the essential aspects of modern C++ programming. We believe its absence would represents a significant hole in quality of programmer life when using C++/CLI. Personally, that is my deep belief.


So, with regard to parameterized types, it felt imperative that we provide some mechanism beyond what was offerred by the System::Collections namespace. The first mechanim that naturally comes to mind to the C++ programmer, of course, is templates. But what about generics? Why couldn’t C++/CLI use generics for containing the CLI types, and leave templates for the non-CLI types? Why map the template mechanism into C++/CLI to support the CLI reference class, value class, interface class, delegate, and function?


The honest answer is because we were left with no choice. One of the generic talking points in presentations, specifications, and hallway whispering, is that generics, while borrowing from C++ template, “does not suffer many of the complexities” of C++ templates.


What are considered as superfluous complexities of C++ templates and were therefore eliminated from the generic mechanism – partial template specialization, the ability to inherit from the type parameter, support for non-type and template type parameters, the ability to specialize either an entire or selected members of a template, and so on – are considered by professional C++ programmers and designers of the language as essential modern programming design patterns that are fundamental to existing production code and widely-used libraries, such as the STL, LOKI, and Boost.


The real problem is that although the C++ community and language designers and implementors have deep experience with parameterized types, that experience was not tapped while the design of generics were underway.


So, we did not have any choice but to provide support of templates for CLI types and to provide an STL.NET implementation. This is great stuff if you care about C++ and want to see it succeed under .NET. Except for performance issues, C++/CLI, in my biased opinion, is shaping up as the premier C++ experience available. Personally, I’m so keen on the new language that I’m planning to reimplement my mscfront translator into the C++/CLI code from native. I’ll be reporting my progress on that in quite some detail in a series of blog entries once I get the C++/CLI text I’m working on in shape.  


So, that’s why we have templates. Why did we also provide support for generics? Generics are deeply integrated into the CLI, and for that reason solve a number of problems left unsolved in C++ – in particular, the instantiation model. Because there is no concept of a runtime within native C++, there is no native concept of how a template is instantiated[1] – that is, when the binding of an actual parameter to the formal parameter occurs and to what extent. The work of the ISO committee in this area has not been stellar.


Generics provides a constraint mechanism, something whose absence is keenly felt in the template mechanism.[2] A generic type is recognized by the CIL – the intermediate language; a template class is not, and so template classes are not cross-language and, it turns out, not cross-assembly as well. That is to say, every serious CLI language has to provide generic support. And that is what we do. Perhaps if we had been participants in the design, the outcome could have been different. But that, to repeat my aunt’s favorite refrain, is water under the bridge.


So, from my perspective, that is why we support both the template and generic solutions for parameterized types, and have tried to integrate them into a elegant symmetry.


 


 


 








[1] Not surprisingly, the vocabulary for the different aspects and actions on a parameterized types are quite different between generics and templates – and I am not going down that path.



[2] Bjarne’s original Usenix template paper included a discussion of constraint syntax, which he then chose not to incorporate into the design. It’s absence means that there is no way to know prior to actual instantiation whether a type is qualified to be used with a particular template class – other than examining the source code – which is not really practicable.


Comments (16)

  1. GO DAWGS says:

    You seem to be more than a little bitter about being excluded from the design of generics. I suspect this is systematic in the C++ community as you are pretty much getting owned by .net and C#. Why don’t you just make the switch? I did, and have never looked back.

  2. stan lippman says:

    i don’t mean to come across as bitter. i actually spent a long time — well, about a year — using C#. it was at the time the only viable window into .net, which is what interests me more than any particular language. my goal, if you will, when i joined microsoft, was to insure that c++ became a viable .net language — and that has always meant being somewhat obnoxious, which of course is not my essential nature :-)

    and i believe this has happened — C++ is imo a better window into .net that any other language — however, it is only a window, and the work is uneven at best. but the effort is very satisfying and i suspect surprised many people who had written c++ off — just as the java community did back in the mid-1990s.

    personally, i think you are the poorer for having never looked back — and i would argue that you diminish your toolbox into .net by holding to that view.

    but, the main point is, there is nothing to be bitter about; this is how things work. one just has to focus and keep what’s important in mind, and not just throw off the past because things currently aren’t going well. one can alternatively try to make things better — and i think we have done that with visual c++ 2005, which is now available and begins to compete. how could i be bitter about that? so, if i came across that way, i did not mean to, and i hope this response goes a small way in offsetting that impression …

    stan

  3. GO DAWGS says:

    Sorry, I was just mad ’cause my job sucks. I was trying to joke but it didn’t really work. Anyway, thanks for the great blog entry.

  4. Nish says:

    I personally believe that while C++/CLI might appear (initially) to have a tougher syntax than languages like C#, eventually it will be the #1 language for core-programmers to target .NET.

    Of course it’s just my opinion here, not sure how much that counts.

  5. LDC says:

    I think C++ is too complex now…faint!

  6. stan lippman says:

    well, these two comments together fairly represent a continuum … this is why there are multiple languages available … to serve each sensibility. cheers.

  7. I would just like to comment on complexity of templates as a reason to use generics instead.

    IMO, there are two distinct groups of people that use templates: those who write template libraries, and those who use template libraries.

    Now, writing template libraries is tough. Templates used in this manner are complex, hard to debug, and often library writers use horrible hacks to work around some limitations of templates. However, only a small minority of C++ programmers ever gets a chance to be in that roles, and those who do are masters like Alexandrescu, Stepanov, Maddock, etc – they are not afraid of "complexities".

    On the other hand, template library can (and indeed, often do) expose clean, logical, easy to use interface to application programmers. When you use e.g. Loki you don’t really meet any of those "terribly complex" aspects of template programming – the burden was on the author of the library.

    Therefore, I would say that all this talk about "complex templates" and "simple generics" is in practice a non-issue for the vast majority of programmers. The only "real-workld" problem with using template libraries I can think of is not-quite-readable compile error messages. It seems that generics are better in this regard. As for two phase name lookup and other subtleties, only a small number of people ever need to care about them.

  8. stan lippman says:

    thank you for writing.

  9. LDC says:

    But when C++ is easier to use, there will be more people to write libraries, isn’t it?

  10. Nemanja Trifunovic says:

    Not if that "ease of use" comes at the expense of power and flexibility. With generics, it is just not possible to make libraries like Loki, MTL, most Boost libraries…

    Generics are great for making type-safe collections, but that’s (almost) it. That is the price for its "simplicity".

  11. stan lippman says:

    well, i don’t think we have enough experience with generics to be so dismissive. the real benefit of generics right now, however, are (a) MSIL/meta-data support, (b) cross-assembly visibility [templates are internal to an assembly], and, of course, (c) cross-language visibility.