"Exceptions are perfectly well allowed to propagate out of destructors"

...and the default behavior is program termination.

"destructors are called during stack unwinding when another exception is propagating, and throwing an exception during another exception causes program termination"

That was true in C++98. In C++11, the default behavior of a destructor is to call std::terminate if an exception propagates out of the destructor. This must be explicitly overridden by the programmer in order to get C++98-style behavior, which is undefined (not sure if that is better or worse than being defined as "unconditional program termination").

This does not need to be a problem. If the stack were unwound after the catch block exits (or if it explicitly unwinds the stack), there would be no double exception faults. This is not something that needs to be inefficient, nor does it need to impact performance any worse than exceptions do right now, and it has already been implemented in other languages (e.g. Common Lisp).

"what's your solution to the problem?"

See above.

"If the destructor is doing nothing but deallocating memory (and calling other destructors that only deallocate memory) then it never needs to throw and there is no problem"

Except that destructors can free other resources, and some of those other resources might throw exceptions when they are freed. A destructor might, for example, close a file; if the file cannot be synchronized when it is closed, an exception should be thrown (but the C++ standard actually requires that such an exception be ignored; after all, the programmer's job is to manually close files, right?).

"If it's doing something else then it's either doing something that another language wouldn't allow because it has no destructors (in which case you can refrain from doing it in C++ as easily as you can switch to one of those languages), or the other language has an equivalent to destructors and then will have the same issue with throwing during exception propagation. Do you see some solution to having it both ways?"

See above! There should not be any program code that cannot safely throw an exception to indicate an error, and the solution is to change the order of stack unwinding and catch block execution. C++ is not the only language with a double-exception problem; Java and Python have this problem also (e.g. in Java, if you have try...finally without any catch and the finally block throws an exception). The other benefit of this approach, aside from solving the double exception problem, is that it makes exceptions more useful by making "restarts" possible: the catch block can resume execution from some point defined by the thrower of the exception, which helps a lot with encapsulation (if e.g. the error can be corrected, but only the client code can correct it; for example, writing to an external disk that was disconnected before the operation could complete).

The only complication is that the catch block needs a stack of its own, and that the catch block needs to have a pointer to the stack frame it should return to if the rest of the stack should be unwound. These are not major challenges, certainly not by comparison with the other challenges C++ compiler writers need to deal with.

"Rubbish. Constructors don't even have return values. Throwing exceptions is the canonical way of indicating construction failure."

Oh, not having return values justifies using exceptions? Destructors don't have return values. How do destructors indicate failures?

Constructor exceptions are bad because they can cause program termination when objects are constructed in the global scope. Global objects are generally bad, of course, since their construction order is undefined, but they are allowed and they are sometimes used by the standard (e.g. cin, cout). This is not a hard one to solve: give programmers a way to catch exceptions that are thrown before main is called or after main exits.

"How about catching the exception, addressing it, and retrying the operation in the try block?"

Except that forces the client code to understand how to retry an operation, which breaks encapsulation, makes error recovery more complicated (if the exception is thrown in the middle of writing some record, you now need a way to either roll back the write, figure out where the incomplete record begins, or figure out where the last write operation failed).

"What do other languages do that you feel is superior?"

Common Lisp's "conditions" system, for one, or any language that supports continuations.

"The range of a 64-bit type is more than sufficient for the overwhelming majority of applications"

Except that integer overflow vulnerabilities are common and problematic, and occur with 64-bit integer types. Saying that 64 bits is enough for any application is kind of like saying that 640k is enough RAM for any application. If you want to use integer arithmetic, use the integers -- which means arbitrary width, or else an exception being thrown if you try to represent an integer that is too big.

"for the few remaining with specialized needs (you know who you are, cryptographers and mathematicians), arbitrary precision libraries are readily available."

Having code that does not do unexpected things is not exactly a "specialized need." It is pretty easy for a programmer to forget that the sum of two positive numbers could actually be negative, or that the product of two positive numbers could be positive but less than the two factors. Most people think of integer arithmetic in terms of integers, not two's complement, even when they are programming in a language with fixed-width arithmetic -- and it takes extra mental effort to remember that you are not really dealing with integers (mental effort that could be used for other things).

Sure, you can use a library -- but then you need to be explicit about wanting arbitrary precision, and you don't get any automatic optimization from your compiler (e.g. if your compiler can prove that some value will always be in a particular range, it might remove an arbitrary precision operation and replace it with a fixed-width operation without creating a risk of an overflow).

"The default string type is std::string"

Unless you declare a string constant, in which case you get a pointer to an array of characters. C++11 conveniently adds yet another way to get a pointer to an array of characters, "raw" string literals (at least it is appropriately named), on top of the typical double-quoted string and the C++03 wide-character string literal notation.

Even with std::string, bounds checking is not guaranteed. operator[] won't check bounds. String iterators can be incremented past the bounds of the string without any exceptions being thrown. You can even try to dereference the placeholder iterator returned by string::end(), with conveniently undefined behavior (would any definition actually make sense for some application or platform?). You have at(), of course, which will do bounds checking but which breaks the [] syntax you get with C-style strings, arrays, maps, and other types (at least you have the same interface as std::vector, which might be useful).

This is the sort of brittleness that characterizes C++ and that makes reliable code hard to write. Everything looks great, until you step over the poorly-indicated boundary that separates "good for C++" and "you'll regret this later."