FW: Boost Digest, Vol 2929, Issue 4

David, I have some responses below ...
------------------------------
Message: 13 Date: Tue, 01 Jun 2010 16:16:25 -0400 From: David Abrahams <dave@boostpro.com> To: boost@lists.boost.org Cc: "mathias.gaunard@ens-lyon.org" <mathias.gaunard@ens-lyon.org> Subject: Re: [boost] Review of a safer memory management approach for C++? Message-ID: <m2hblmcxuu.wl%dave@boostpro.com> Content-Type: text/plain; charset=UTF-8
...
At Thu, 27 May 2010 20:02:45 +0200, Ingo Loehken wrote:
if I understand "hard to reason about" in the right why : like
is no need for shared ownership at all, this also means that
there there
is no use for COM Programming - and of course there is.
I think you don't understand it the right way. Shared ownership (at least in the presence of mutation) is hard to reason about because seemingly-local modifications can have non-local effects.
[Bartlett, Roscoe A]
Yes, but let's focus on the "essential complexity" of object sharing
Okay.
and not get bogged down in the "accidental complexity" of memory management. The approaches described in
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
make explicit the "essential complexity" of object sharing while trying to eliminate the "accidental complexity" of memory management (see Section 6.1 in http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf).
I'm all for making essential complexity obvious and eliminating accidental complexity, but I'm not sure I see a compelling example that Teuchos does that, at least not when compared with what I consider normal state-of-the-art C++ programming, which is mostly value-based, dynamic allocations are rare, and those that occur are immediately managed, e.g. by appropriate smart pointers.
[Bartlett, Roscoe A] I don't want to get into an argument about the "proper" way to use C++ here (but I will a little below). In Item 1 of "Effective C++, 3rd Edition", Scott Meyers identifies four different major programming paradigms that are supposed to be supported by C++: 1) C: raw pointers, low-level built-in datatypes (int, double, etc.), statements, blocks, functions etc. 2) Object-oriented C++: Inheritance, virtual functions, dynamic allocation, RTTI, etc. (i.e. runtime polymorphism) 3) Template C++: Typical generic programming, template metaprogramming, etc. (i.e. compile-time polymorphism) 4) STL: Containers, iterators, algorithms, etc. and code like this (not just using STL) (closely related to #3 Template C++). Of course any well written large C++ program is a mixture of all four of the above (and more). What you seem to be arguing above is that most good C++ software will use templates in #3 and #4 but almost none of #2 object-oriented C++. Note that C++ was first designed in the early 80s primarily to support #2 object-oriented C++. I can see the reason for this bias for #3 and #4 given the nature of most of the Boost libraries and you can have that opinion if you would like. However, there are clearly types of programming problems that call for more of #2 and less of #3 and #4 in the overall design. Languages like Java and Python would be typically used to write safe object-oriented programs in a productive environment (free of complications of dynamic memory allocation and most undefined behavior that is so easy to expose with current C++ approaches). I will argue below that more C++ programs are better served by using more of #2 object-oriented C++ and to a lesser extent template-based methods in #3 and #4 (except where it is called for in lower-level code). Instead of arguing if we should be using #2 object-oriented C++ (runtime polymorphism) or instead prefer #3 template programming (static polymorphism), let's assume that we want C++ to do a good job of supporting #2 object-oriented C++ if we decide to use it. Therefore, the real question is if C++ is indeed supposed to support #2 object-oriented programming, does C++ it support it well enough so that I would choose to use C++ instead of Java or Python (assuming my programming team knows those languages)? By all reasonable measures, writing object-oriented programs in Java or Python is more productive because of garbage collection and the elimination of undefined behavior that is so common in C++ programs (I can find lots of references for these opinions and even data from studies to support this claim). However, to achieve performance in CSE software, I have to write some code in C/C++ or some other appropriate language. No-one has been able to demonstrate sufficient performance for CSE codes written entirely in Java or Python. Also, many of the "safer" and "more productive" OO languages (e.g. Java and Python) are not even available on some high-end HPC platforms. Even if they were, mixed language programming has lots of problems that discourages wide-spread use. That leaves C++ as the only fully portable viable language for high-performance CSE (sorry Fortran programmers). I would argue that the current accepted approaches to writing OO programs in C++ (i.e. #2 OO C++ features) make it too easy to create programs with undefined behavior (i.e. segfaults or worse) and as a result, the paranoia of undefined behavior in C++, memory leaks etc. lead to lots of bad programming practices and designs (see Section 1 in http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf). What the approach described in the Teuchos MM report tries to do is to create a set of classes and idioms that can allow C++ to be used to safely and productively develop #2 OO C++ software closer to the safety and productively that you can get with languages like Java and Python. However, there is still too much "accidental complexity" in C++ to have it ever fully compete with productivity of writing in Java and Python for basic OO features (IMO). In any case, C++ offers other advantages over any other language that would have you choose it over these other languages.
Trying to over design a program to avoid all shared ownership is what make C++ programming so unproductive and has all the negative consequences described in Section 1 in:
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
Designs with object sharing can be much less complex overall than designs without sharing. You just need decent tools to detect circular reference problems (and that is what the Teuchos::RCP class has).
Well, I fundamentally disagree with all of the above. Overuse of runtime polymorphism, thus dynamic allocation, and thus shared ownership (it's almost an inevitable progression) is one of the things that has made C++ programming unproductive, and those people I know who most zealously avoid it tend to be more productive than everyone else. IMO.
[Bartlett, Roscoe A] When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism for the following (indisputable) reasons: 1) Well written OO C++ software builds and (more importantly) rebuilds *much* faster then heavily template code. This massively speeds up the development cycle. The difference can be orders of magnitude of time savings in recompile/relink times. I have seen cases where carefully using runtime OO can result in recompile/relink cycles take less than 20 seconds where using all template code would take 20 minutes. I am *not* exaggerating here at all. 2) Templated programs can result in massive object-code bloat over equivalent runtime OO programs. If you are really sloppy with templates and use it for everything, you can easily double, triple, or more the size of the object code and executables (see Item 44 in "Effective C++, 3ed edition"). 3) The error messages you get when mistakes are made when using interfaces and virtual functions are far superior to the mess that most compliers put out when you make a mistake with implicit compile-time template interfaces. This is a huge issue. Runtime errors, as opposed to cryptic compile-time errors, can be handled by throwing exceptions with very good error messages that programmers can 100% control the quality of. Yes, of course you prefer compile-time errors to runtime errors all things being equal, and am not arguing with that. This is generally not true of template programming (but there are some tricks you can use to improve compile-time error messages, but not perfectly). For example, if you make a mistake with STL iterators/algorithms you can get very cryptic error messages that dumfound most (non-expert and expert) C++ programmers. 4) Well written OO C++ software allows for significant runtime flexibility. If you use dynamic libraries, for instance, you can add new implementations of objects without even having to relink customer executables. Also, you can fix many types of bugs in updated shared libraries without having to recompile/relink customer executables. In a large-scale software setting, this can be very helpful. Believe me, I see the use of template C++ code with lots of static typing and lots of local logic and control but it is not the end all and will never replace classic OO C++ for most high-level designs and software architecture. I challenge anyone to refute this. C++ compilers will need to become many orders of magnitude faster that what they are today for this fact to change (but then the software will just grow that much larger and erase the compiler speed improvements).
In my experience, designs with object sharing tend to increase complexity in all kinds of ways beyond the memory management issues that you describe. Perhaps the most important way is, they tend to obscure the overall computation being performed by hiding it in networks of interacting objects.
[Bartlett, Roscoe A] This is not going to magically change by templating everything. If a lot of objects collaborate together to perform computations, it does not matter if you use static or runtime polymorphism; the code will be hard to figure out and you will need print statements and/or run it in a debugger to see what is really happening.
I would like to see an example of a design with shared object ownership that is ?much less complex? than all other functionally equivalent designs that eschew shared ownership. I'm not saying such applications don't exist, but IME they are rare, and a concrete example would help to bring this whole discussion back to earth.
[Bartlett, Roscoe A] One design where shared mutable objects likely makes things overall simpler and more natural is the PDE expression mechanism in the Sundance code (http://www.math.ttu.edu/~klong/Sundance/html/). If the clients all expect remote updates of shared objects, then things work great. The problem is when an object is changed by another client and you don't expect it to change (i.e. the classic problem of change propagation). As for sharing of objects, it is critical for many types of software in the Computational Science and Engineering (CSE) domain. You have to share large objects in order to cut down on runtime and storage overhead. As one example, consider the Petra object model with the Trilinos packages Epetra and Tpetra (http://trilinos.sandia.gov/). In the Petra object model, you have objects with significant storage overhead like Maps and Graphs that are shared between many different objects like Vectors, MultiVectors, and Matrices. Yes, you could create deep copies of Maps and Graphs but that would waste a large amount of storage. The bottom line is that by sharing large objects like Maps and Graphs instead of making large numbers of copies, we can fit and solve much larger simulation problems on a computer than we could otherwise. You could template and statically allocate everything and you will never get around the fundamental need to share the data contained in large objects in these types of applications. Note, however, that Maps in Epetra/Tpetra are shared as const immutable objects. That means that no-one can change a Map after it is first created. Therefore, all of the problems with unexpected updates goes away (as you mention above). However, the shared Maps must go away when they are not needed anymore and that is what the Teuchos::RCP class makes easy and robust. Under the covers, Tpetra uses Tpetra::ArrayRCP for similar purposes and much more. The situation with Graphs is different and issues of change propagation of shared objects are still present in some cases. Come to think of it, most of the object sharing that used in the CSE software that I use and write mostly just share const immutable objects so problems of change propagation mostly goes away. However, there are some important use cases where all of the clients are not holding RCPs to const objects and the problem of change propagation remains. Again, to save in significant runtime and storage overhead, we can't just create deep copies of all of these objects. Templating and purely stack-based programs are not going to solve that problem. The Teuchos MM approach, I believe, largely solves the memory management problems with dynamic memory allocation and object sharing while still yielding very high performance and mostly eliminating undefined behavior associated with the incorrect usage of memory. The most significant contribution of the Teuchos MM approach is the presents of Teuchos::Ptr and Teuchos::ArrayView and how they interact with the other classes. Cheers, - Ross

On 6/3/2010 4:29 PM, Bartlett, Roscoe A wrote:
David,
I have some responses below ...
------------------------------
Message: 13 Date: Tue, 01 Jun 2010 16:16:25 -0400 From: David Abrahams<dave@boostpro.com> To: boost@lists.boost.org Cc: "mathias.gaunard@ens-lyon.org"<mathias.gaunard@ens-lyon.org> Subject: Re: [boost] Review of a safer memory management approach for C++? Message-ID:<m2hblmcxuu.wl%dave@boostpro.com> Content-Type: text/plain; charset=UTF-8 [...] I would like to see an example of a design with shared object ownership that is ?much less complex? than all other functionally equivalent designs that eschew shared ownership. I'm not saying such applications don't exist, but IME they are rare, and a concrete example would help to bring this whole discussion back to earth.
[Bartlett, Roscoe A]
One design where shared mutable objects likely makes things overall simpler and more natural is the PDE expression mechanism in the Sundance code (http://www.math.ttu.edu/~klong/Sundance/html/). If the clients all expect remote updates of shared objects, then things work great. The problem is when an object is changed by another client and you don't expect it to change (i.e. the classic problem of change propagation).
As for sharing of objects, it is critical for many types of software in the Computational Science and Engineering (CSE) domain. You have to share large objects in order to cut down on runtime and storage overhead. As one example, consider the Petra object model with the Trilinos packages Epetra and Tpetra (http://trilinos.sandia.gov/). In the Petra object model, you have objects with significant storage overhead like Maps and Graphs that are shared between many different objects like Vectors, MultiVectors, and Matrices. Yes, you could create deep copies of Maps and Graphs but that would waste a large amount of storage. The bottom line is that by sharing large objects like Maps and Graphs instead of making large numbers of copies, we can fit and solve much larger simulation problems on a computer than we could otherwise. You could template and statically allocate everything and you will never get around the fundamental need to share the data contained in large objects in these types of applications.
Note, however, that Maps in Epetra/Tpetra are shared as const immutable objects. That means that no-one can change a Map after it is first created. Therefore, all of the problems with unexpected updates goes away (as you mention above). However, the shared Maps must go away when they are not needed anymore and that is what the Teuchos::RCP class makes easy and robust. Under the covers, Tpetra uses Tpetra::ArrayRCP for similar purposes and much more. The situation with Graphs is different and issues of change propagation of shared objects are still present in some cases.
Come to think of it, most of the object sharing that used in the CSE software that I use and write mostly just share const immutable objects so problems of change propagation mostly goes away. However, there are some important use cases where all of the clients are not holding RCPs to const objects and the problem of change propagation remains. Again, to save in significant runtime and storage overhead, we can't just create deep copies of all of these objects. Templating and purely stack-based programs are not going to solve that problem.
The Teuchos MM approach, I believe, largely solves the memory management problems with dynamic memory allocation and object sharing while still yielding very high performance and mostly eliminating undefined behavior associated with the incorrect usage of memory. The most significant contribution of the Teuchos MM approach is the presents of Teuchos::Ptr and Teuchos::ArrayView and how they interact with the other classes.
Cheers,
- Ross
I don't see how shared object *ownership* is necessary in this example... :/ I don't think Dave was arguing that holding large objects by reference, or even dynamically allocating objects (large or not), is necessarily complex or "bad"... Or did I miss something? - Jeff

At Thu, 3 Jun 2010 17:29:15 -0600, Bartlett, Roscoe A wrote:
David,
I have some responses below ...
Message: 13 Date: Tue, 01 Jun 2010 16:16:25 -0400 From: David Abrahams <dave@boostpro.com>
I'm all for making essential complexity obvious and eliminating accidental complexity, but I'm not sure I see a compelling example that Teuchos does that, at least not when compared with what I consider normal state-of-the-art C++ programming, which is mostly value-based, dynamic allocations are rare, and those that occur are immediately managed, e.g. by appropriate smart pointers.
[Bartlett, Roscoe A]
I don't want to get into an argument about the "proper" way to use C++ here (but I will a little below). In Item 1 of "Effective C++, 3rd Edition", Scott Meyers identifies four different major programming paradigms that are supposed to be supported by C++:
1) C: raw pointers, low-level built-in datatypes (int, double, etc.), statements, blocks, functions etc.
2) Object-oriented C++: Inheritance, virtual functions, dynamic allocation, RTTI, etc. (i.e. runtime polymorphism)
3) Template C++: Typical generic programming, template metaprogramming, etc. (i.e. compile-time polymorphism)
4) STL: Containers, iterators, algorithms, etc. and code like this (not just using STL) (closely related to #3 Template C++).
Yes, I'm aware of that way of dividing up the picture.
Of course any well written large C++ program is a mixture of all four of the above (and more).
I don't take it for granted that any well-written large C++ program will use any given paradigm.
What you seem to be arguing above is that most good C++ software will use templates in #3 and #4 but almost none of #2 object-oriented C++.
Did I say anything about templates? No. I _will_ argue that OOP has costs to software complexity that are often under-appreciated.
Note that C++ was first designed in the early 80s primarily to support #2 object-oriented C++. I can see the reason for this bias for #3 and #4 given the nature of most of the Boost libraries and you can have that opinion if you would like.
I don't, so please read what I wrote and try to address that rather than to your preconceived notions of my “bias.”
I would argue that the current accepted approaches to writing OO programs in C++ (i.e. #2 OO C++ features) make it too easy to create programs with undefined behavior (i.e. segfaults or worse) and as a result, the paranoia of undefined behavior in C++, memory leaks etc. lead to lots of bad programming practices and designs (see Section 1 in http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf).
Seriously? Are you saying that a disciplined application of shared_ptr, weak_ptr, new_shared, et. al when dynamic allocations are called for is insufficient for most OO programs?
Trying to over design a program to avoid all shared ownership is what make C++ programming so unproductive and has all the negative consequences described in Section 1 in:
http://www.cs.sandia.gov/~rabartl/TeuchosMemoryManagementSAND.pdf
Designs with object sharing can be much less complex overall than designs without sharing. You just need decent tools to detect circular reference problems (and that is what the Teuchos::RCP class has).
Well, I fundamentally disagree with all of the above. Overuse of runtime polymorphism, thus dynamic allocation, and thus shared ownership (it's almost an inevitable progression) is one of the things that has made C++ programming unproductive, and those people I know who most zealously avoid it tend to be more productive than everyone else. IMO.
[Bartlett, Roscoe A]
When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism
Straw man; I'm not arguing for “templated C++ programs using static polymorphism.”
In my experience, designs with object sharing tend to increase complexity in all kinds of ways beyond the memory management issues that you describe. Perhaps the most important way is, they tend to obscure the overall computation being performed by hiding it in networks of interacting objects.
[Bartlett, Roscoe A]
This is not going to magically change by templating everything.
Straw man; I'm not arguing for “templating everything.”
If a lot of objects collaborate together to perform computations, it does not matter if you use static or runtime polymorphism; the code will be hard to figure out and you will need print statements and/or run it in a debugger to see what is really happening.
Yes, and an “OO mindset” has tended to lead to partitioning systems so that “a lot of objects collaborate together to perform computations,” which tends to obscure the computation performed.
I would like to see an example of a design with shared object ownership that is “much less complex” than all other functionally equivalent designs that eschew shared ownership. I'm not saying such applications don't exist, but IME they are rare, and a concrete example would help to bring this whole discussion back to earth.
[Bartlett, Roscoe A]
One design where shared mutable objects likely makes things overall simpler and more natural is the PDE expression mechanism in the Sundance code (http://www.math.ttu.edu/~klong/Sundance/html/). If the clients all expect remote updates of shared objects, then things work great.
I'm sorry, your reference isn't specific enough for me to see the design piece you're referring to. -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Hi Ross, On Jun 3, 2010, at 5:29 PM, Bartlett, Roscoe A wrote: Take my comments with a grain of salt as I'm biased having interacted with both you and Trililnos for the last decade or so.
I will argue below that more C++ programs are better served by using more of #2 object-oriented C++ and to a lesser extent template-based methods in #3 and #4 (except where it is called for in lower-level code).
Someone, I think David, asked for a concrete example. A well known example is matrices and, for exposition, the additive schwarz algorithm described in Saad's Iterative Methods for Sparse Linear Systems, page 401, section 13.3.3, available here: http://www-users.cs.umn.edu/~saad/PS/all_pdf.zip The serial implementation of this algorithm basically requires A, x, b, and A_inv (the inverse of A) where the upper-case variables (A and A_inv) are matrices and lower-case (x, b) are vectors. A naive OO design might construct a base class called matrix with a virtual invert method to invert a matrix. A dense matrix class derived from the base matrix class could correctly and efficiently (inverse is n^3) support two different techniques for the inverse operation whereas most sparse matrix representations (row or column indexed sparse storage, etc.) could not effectively implement an inverse. That is, it's usually erroneous to attempt to invert a sparse representation unless it's specially designed to support it. Note that argument A to the additive schwarz algorithm would work just fine with any matrix representation, sparse or dense, provided it supports the common matvec operation. My point here is that it might seem reasonable to a less experienced developer to use an OO approach and declare a matrix base class with a virtual invert method but I think more seasoned developers would immediately recognize that approach as flawed. Invoking the additive schwarz algorithm with a sparse representation for the A_inv argument is usually wrong and is easily diagnosable at compile time. In this case, I think most users would prefer a compile error rather than have their program fail right after it starts to run, especially for CSE HPC applications that have been sitting in the queue for hours waiting to run. Bundling functionality together through OO composition often imposes significant comprehension complexity (lots of base -> derived casting, switch statements, ...), as the hierarchies are often "not quite right". More important, OO hierarchies typically impose a significant barrier impeding cooperation with independently developed libraries in related domains (as you know, this is a big deal in the multi-physics arena). I do think OO has its place but I find that instead of using OO more in new development efforts, I use it much, much less. Respectfully, -- Noel

on Sat Jun 05 2010, "Belcourt, Kenneth" <kbelco-AT-sandia.gov> wrote:
Hi Ross,
On Jun 3, 2010, at 5:29 PM, Bartlett, Roscoe A wrote:
Take my comments with a grain of salt as I'm biased having interacted with both you and Trililnos for the last decade or so.
In other words, unlike me, you know what you're talking about :-)
I will argue below that more C++ programs are better served by using more of #2 object-oriented C++ and to a lesser extent template-based methods in #3 and #4 (except where it is called for in lower-level code).
Someone, I think David, asked for a concrete example. A well known example is matrices and, for exposition, the additive schwarz algorithm described in Saad's Iterative Methods for Sparse Linear Systems, page 401, section 13.3.3, available here:
This is to say nothing of the fact that OO is incapable of representing even the simplest binary mathematical operations polymorphically without losing type safety (http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.39.8837). OO has its place, but the binary method problem is such a huge obstacle for the design of mathematical frameworks that I'm very surprised to hear arguments that OO serves most C++ programs best coming from someone in the HPC community. P.S. what's the frequency, Kenneth? Does Noel have a brother at Sandia or…? -- Dave Abrahams BoostPro Computing http://boostpro.com

On Jun 5, 2010, at 6:40 AM, David Abrahams wrote:
P.S. what's the frequency, Kenneth? Does Noel have a brother at Sandia or…?
No, last I checked there was only one of me (regrettably my email does come through as Kenneth and I can't change that). -- Noel

At Sat, 05 Jun 2010 08:40:52 -0400, David Abrahams wrote:
on Sat Jun 05 2010, "Belcourt, Kenneth" <kbelco-AT-sandia.gov> wrote:
Hi Ross,
On Jun 3, 2010, at 5:29 PM, Bartlett, Roscoe A wrote:
Take my comments with a grain of salt as I'm biased having interacted with both you and Trililnos for the last decade or so.
In other words, unlike me, you know what you're talking about :-)
This remark, which was only meant to be humorously self-deprecating, came off in context of Noel's critical post as an attack. I apologize to Ross and everyone else on the list for that. Thanks for your patience, -- Dave Abrahams BoostPro Computing http://www.boostpro.com

Noel,
-----Original Message----- From: Belcourt, Kenneth Sent: Friday, June 04, 2010 10:10 PM To: boost@lists.boost.org Cc: Bartlett, Roscoe A Subject: Re: Review of a safer memory management approach for C++?
...
I will argue below that more C++ programs are better served by using more of #2 object-oriented C++ and to a lesser extent template-based methods in #3 and #4 (except where it is called for in lower-level code).
Someone, I think David, asked for a concrete example. A well known example is matrices and, for exposition, the additive schwarz algorithm described in Saad's Iterative Methods for Sparse Linear Systems, page 401, section 13.3.3, available here:
http://www-users.cs.umn.edu/~saad/PS/all_pdf.zip
The serial implementation of this algorithm basically requires A, x, b, and A_inv (the inverse of A) where the upper-case variables (A and A_inv) are matrices and lower-case (x, b) are vectors. A naive OO design might construct a base class called matrix with a virtual invert method to invert a matrix.
[Bartlett, Roscoe A] This is a bad example that has nothing to do with OO specifically. Mathematically, it is not true that *every* matrix is invertible so you should not add an appyInverse(...) to a general matrix interface (either with runtime or compile-time polymorphism). It breaks even the mathematical definition of a linear operator or matrix. Note that Thyra::LinearOpBase does not have an applyInverse(...) for this reason. If you write a templated algorithm that expects to call appyInverse(...) with a compile-time implicit interface, your code is just as wrong as with the runtime OO implementation (except the templated code takes 10 to 1000 times longer to recompile and relink if it does not segfault the compiler first).
My point here is that it might seem reasonable to a less experienced developer to use an OO approach and declare a matrix base class with a virtual invert method but I think more seasoned developers would immediately recognize that approach as flawed. Invoking the additive schwarz algorithm with a sparse representation for the A_inv argument is usually wrong and is easily diagnosable at compile time. In this case, I think most users would prefer a compile error rather than have their program fail right after it starts to run, especially for CSE HPC applications that have been sitting in the queue for hours waiting to run.
[Bartlett, Roscoe A] So are you suggesting that everyone template all of their code and use implicit template instantiation? That is simply not possible with current software, compilers, and platforms. I can *prove* this with code, compilers, and platforms that we have right now. I have lots of examples. How to you respond to the following? When runtime performance or other issues related to dynamic allocations and classic OO are not a problem, classic OO C++ programs using runtime polymorphism are typically superior to highly templated C++ programs using static polymorphism for the following (indisputable) reasons: 1) Well written OO C++ software builds and (more importantly) rebuilds *much* faster than heavily templated code. This massively speeds up the development cycle. The difference can be orders of magnitude of time savings in recompile/relink times. I have seen cases where carefully using runtime OO can result in recompile/relink cycles take less than 20 seconds where using all template code would take 20 minutes. I am *not* exaggerating here at all. 2) Current compilers may not even be able to compile large amounts of template code at all. There are compilers that we have to support (e.g. Sun and IBM) that will segfault or return "internal compiler error" when you give them too much template code. These same compilers have no problem with virtual functions with standard OO. This is reality. 3) Templated programs can result in massive object-code bloat over equivalent runtime OO programs. If you are really sloppy with templates and use it for everything, you can easily double, triple, or more (10x) the size of the object code and executables (see Item 44 in "Effective C++, 3ed edition"). 4) The error messages you get when mistakes are made when using explicit interfaces and virtual functions are far superior to the mess that most compliers put out when you make a mistake with implicit compile-time template interfaces. This is a huge issue. Runtime errors, as opposed to cryptic compile-time errors, can be handled by throwing exceptions with very good error messages that programmers can 100% control the quality of. Yes, of course you prefer compile-time errors to runtime errors all things being equal, and am not arguing to prefer runtime errors over compile-time errors in general. This is generally not true of template programming (there are some tricks you can use to improve compile-time error messages, but not perfectly). For example, if you make a mistake with STL iterators/algorithms you can get very cryptic error messages that dumfound most (non-expert and expert) C++ programmers. 5) Well written OO C++ software allows for significant runtime flexibility. If you use dynamic libraries, for instance, you can add new implementations of objects without even having to relink customer executables. Also, you can fix many types of bugs in updated shared libraries without having to recompile/relink customer executables. In a large-scale software setting, this can be very helpful. ******************************************************************************************** *** You can't portably compile and link large C++ programs that template everything!!!!! *** ******************************************************************************************** As long as people keep suggesting that we template all of our codes for every little reason, I am going to keep reminding people of the realities of template code in C++. If you can't respond to the above points (especially #1 and #2) then there is little point in continuing the discussion. Thinking that we are going to drop runtime polymorphism and use exclusively compile-time polymorphism for everything is just fantasy land. - Ross

Bartlett, Roscoe A wrote:
1) Well written OO C++ software builds and (more importantly) rebuilds *much* faster than heavily templated code. This massively speeds up the development cycle. The difference can be orders of magnitude of time savings in recompile/relink times. I have seen cases where carefully using runtime OO can result in recompile/relink cycles take less than 20 seconds where using all template code would take 20 minutes. I am *not* exaggerating here at all.
You have the right to write proper, fast to compile template code. Technics and tools exists for this. And if your tempalte takes 20mn to compile, your template design is wrong ... In our own HPC template base code, everything compiles under 20s and only some exhaustive unti test that instanciate all and every type/functiosn took up to 5mn.
2) Current compilers may not even be able to compile large amounts of template code at all. There are compilers that we have to support (e.g. Sun and IBM) that will segfault or return "internal compiler error" when you give them too much template code. These same compilers have no problem with virtual functions with standard OO. This is reality.
Sun and IBM are all but references on the point of tempalte handling ... gcc, MSVC, ICC and even clang don't choke on most templated code ... What about stopping working with compilers from before the industrial revolution ?
3) Templated programs can result in massive object-code bloat over equivalent runtime OO programs. If you are really sloppy with templates and use it for everything, you can easily double, triple, or more (10x) the size of the object code and executables (see Item 44 in "Effective C++, 3ed edition").
same than 1/ learn to do it right ... And well, since when gigabyte started to be costly ?
4) The error messages you get when mistakes are made when using explicit interfaces and virtual functions are far superior to the mess that most compliers put out when you make a mistake with implicit compile-time template interfaces. This is a huge issue. Runtime errors, as opposed to cryptic compile-time errors, can be handled by throwing exceptions with very good error messages that programmers can 100% control the quality of. Yes, of course you prefer compile-time errors to runtime errors all things being equal, and am not arguing to prefer runtime errors over compile-time errors in general. This is generally not true of template programming (there are some tricks you can use to improve compile-time error messages, but not perfectly). For example, if you make a mistake with STL iterators/algorithms you can get very cryptic error messages that dumfound most (non-expert and expert) C++ programmers.
Use SFINAE, Concept Checks and static assert ...
5) Well written OO C++ software allows for significant runtime flexibility. If you use dynamic libraries, for instance, you can add new implementations of objects without even having to relink customer executables. Also, you can fix many types of bugs in updated shared libraries without having to recompile/relink customer executables. In a large-scale software setting, this can be very helpful.
That's maybe th eonyl poitn where I agree with you.
******************************************************************************************** *** You can't portably compile and link large C++ programs that template everything!!!!! *** ********************************************************************************************
Why ?
As long as people keep suggesting that we template all of our codes for every little reason, I am going to keep reminding people of the realities of template code in C++. If you can't respond to the above points (especially #1 and #2) then there is little point in continuing the discussion. Thinking that we are going to drop runtime polymorphism and use exclusively compile-time polymorphism for everything is just fantasy land.
Nobody told you to put template everywhere. Same we don't want to put dynamic polymorphism everywhere. What about learnign to sue the proper tool for the proper task ?

05/06/2010 15:02, Bartlett, Roscoe A wrote:
1) Well written OO C++ software builds and (more importantly) rebuilds *much* faster than heavily templated code. This massively speeds up the development cycle. The difference can be orders of magnitude of time savings in recompile/relink times. I have seen cases where carefully using runtime OO can result in recompile/relink cycles take less than 20 seconds where using all template code would take 20 minutes. I am *not* exaggerating here at all.
One additional factor you have to take when writing template code is compile-time efficiency. It is true it adds an additional dimension to the difficulty of designing and writing software.
2) Current compilers may not even be able to compile large amounts of template code at all. There are compilers that we have to support (e.g. Sun and IBM) that will segfault or return "internal compiler error" when you give them too much template code. These same compilers have no problem with virtual functions with standard OO. This is reality.
It's up to you whether you want to support those compilers or not. I myself see little compelling reason to support the Sun compiler, almost likewise for IBM, at least for new C++ projects. Balance your support for legacy compilers with your willingness to have nice code.
3) Templated programs can result in massive object-code bloat over equivalent runtime OO programs. If you are really sloppy with templates and use it for everything, you can easily double, triple, or more (10x) the size of the object code and executables (see Item 44 in "Effective C++, 3ed edition").
They can, but they don't have to. If your template function is not a good inlining candidate, is likely to get instantiated with many different types and and if making it runtime-generic has sufficiently low costs, then you should consider doing it. Note, however, that the size costs are usually grossly exaggerated. I've seen people use advanced template C++ code on microcontrollers (the kind with a few kilobytes of memory) just fine.
4) The error messages you get when mistakes are made when using explicit interfaces and virtual functions are far superior to the mess that most compliers put out when you make a mistake with implicit compile-time template interfaces. This is a huge issue. Runtime errors, as opposed to cryptic compile-time errors, can be handled by throwing exceptions with very good error messages that programmers can 100% control the quality of. Yes, of course you prefer compile-time errors to runtime errors all things being equal, and am not arguing to prefer runtime errors over compile-time errors in general. This is generally not true of template programming (there are some tricks you can use to improve compile-time error messages, but not perfectly). For example, if you make a mistake with STL it erators/algorithms you can get very cryptic error messages that dumfound most (non-expert and expert) C++ programmers.
Albeit they're long, they're not that cryptic. Also, there are programming techniques to have nice error messages. I would say you can even reach close to the quality of OCaml (for a certain definition of close, I suppose).
5) Well written OO C++ software allows for significant runtime flexibility. If you use dynamic libraries, for instance, you can add new implementations of objects without even having to relink customer executables. Also, you can fix many types of bugs in updated shared libraries without having to recompile/relink customer executables. In a large-scale software setting, this can be very helpful.
You can use type erasure to achieve dynamic polymorphism while keeping a non-intrusive, value-based and template-based generic design.
As long as people keep suggesting that we template all of our codes for every little reason
No one is suggesting this. Make your code only as generic as your algorithms intrinsically are, no more. Algorithms are what lay at the center of generic programming, not objects. This is quite a different view from OOP.
Thinking that we are going to drop runtime polymorphism and use exclusively compile-time polymorphism for everything is just fantasy land.
Compile-time polymorphism is better, and you can build runtime polymorphism on top of it with type erasure. So why avoid compile-time polymorphism when you can do it?
participants (6)
-
Bartlett, Roscoe A
-
Belcourt, Kenneth
-
David Abrahams
-
Jeffrey Lee Hellrung, Jr.
-
joel falcou
-
Mathias Gaunard