The Microsoft support desk continues under Bobby's stewardship, even as he aspires to discuss Standard C++.
Copyright © 2000 Robert H. Schmidt
I finally have Borland's C++ Builder installed. I've actually had the compiler for several months, but encountered delays before getting it to work.
First of all, the compiler showed up during my recent two-month sojourn with family in Sedona. Given the purpose of that trip, I really wasn't in the mood to play with new software.
Once I returned home and started getting my life somewhat back together (a slow ongoing process), I had to buy a PC emulator and Windows to host the compiler. Once installed, the compiler kept bombing until I downloaded and installed the update from Borland's web site.
With that accomplished, I am now faced with the never-pleasant task of configuring the build environment to my taste. I can make the compiler work; I just can't make it work the way I want. I'm finding that Borland like Microsoft suffers from what I call McDonald's Syndrome.
If you hit the drive-through at McDonald's and order something they're expecting, you can get your food and be gone in no time. But if your order deviates from their norm, life becomes complex: you are banished to a Special Order gulag in the parking lot, wait an interminable time for someone to deliver your food, then hope that the special order is correct and complete.
Similarly, development environments make life quick and easy if you follow their paths of least resistance. But if you want to create something other than a Windows program loaded with widget resources and foundation classes, that same software becomes indifferent or even hostile. Having endured myriad IDE installations and project wizards, I've begun capitulating to the vendors' wills: instead of crafting lean installations and projects, I often take 100% of the default options just to get the 5% I really need.
For the Borland compiler, all I want is to create Standard-conforming programs that pull source files from where I pick and produce output files where I pick. (I typically share source files among multiple translators and OSes, so that I can easily compare translator behavior.) I'll spare you the gory details, and simply say that I've yet to accomplish this desire with Borland's compiler but I'm determined to lick this problem by next month.
Unsolved Mysteries
Q
Bobby,
I am reading Scott Meyers' books and trying to learn his exception rules and hints. In More Effective C++ (Item 14) he says not to put exception specifications on template functions. I like the idea of exception specifications, and want to better understand Scott's reasons. So I made a simple template class, and tested adding exception specifications.
During these tests I hit a problem: I couldn't find a way to keep my template's constructors from throwing exceptions. Could you look at my hopefully simple code and let me know if I'm missing something? Thank you!
P.S. I use VC 6, if that helps you.
Mel Czezlydk
A
Sweet serendipity I'm researching this same problem for a different (non-CUJ) project. I don't yet have the full answer, but I can tell you what I know, and promise to tell the rest when I know it.
I've reduced your sample to the following:
#include <stdio.h> class M { public: M() { printf("M throw\n"); throw 0; } }; class X { public: X() : m() { } private: M m; }; int main() { X x; printf("main end\n"); }While my example (unlike yours) omits templates and inheritance, the root problem is the same: preventing the construction of x from throwing exceptions.
When run, the above program produces:
M throwApparently the program ends abnormally instead of returning from main. This behavior isn't surprising, since the x.m exception bubbles up the call stack uncaught and uncaught exceptions eventually cause a program to self-destruct [1].
At a minimum we need to catch the exception. You already tried
X() : m() { try { } catch (...) { printf("X catch\n"); } }but were disappointed to find that the exception wasn't caught. Once again, the behavior isn't surprising: before your try block is entered, x.m is already constructed and the exception is already thrown. Your try block comes into play too late.
You must be ready to catch the exception while x.m undergoes construction. That capability requires a feature relatively new to C++: a function try block. Unfortunately for you, Visual C++ does not support function try blocks [2]. As long as you stay with that compiler, you can't catch the exception before it trickles out of x's constructor.
If you switch to a translator that supports function try blocks, you can catch the exception:
X() try : m() { } catch (...) { printf("X catch\n"); }Note that m() appears after the try, suggesting that the construction of X::m happens within the control of the try block. If you run this example on a conforming implementation, the result is:
M throw X catchWhat you don't see is evidence of normal termination. Implication: even though the program catches the exception, it still ends abnormally! What's going on?
As usual, the C++ Standard provides illumination. From subclause 15.3 ("Handling an exception"):
The exception being handled is rethrown if control reaches the end of a handler of the function-try-block of a constructor or destructor.
Apparently our constructor's handler acts as if it were written
catch (...) { printf("X catch\n"); throw; }To test this assumption, run the program version shown as Listing 1. The result is
M throw X catch main catch main endThis confirms my suspicions: the exception is caught by the function try block, then implicitly rethrown.
Adding an empty exception specification won't help:
X() throw()Regardless of what the specification says, we know that X::X will throw anyway. Violating an exception specification results in a call to std::unexpected, which by default terminates execution [3] at least on conforming implementations.
(VC++ isn't conforming here. It accepts exception specifications at compile time [4] but ignores them at run time. Since the resulting programs have no actual specifications to violate, they never call std::unexpected.)
The moral of the story: constructors can catch subobject exceptions but can't contain them.
I now believe that function try blocks on constructors are purposely designed to be exception filters instead of exception handlers. I also believe they implicitly throw to prevent accidental access of unconstructed or partially-constructed objects. I will research these beliefs, and let you know what I discover.
Good Afternoon, Microsoft Product Support
Q
Hi Bobby,
I have a question about template instantiation.
In my short sample there are two overloads of a template function, each with its own argument list. I want to use these functions with explicit template argument types.
For the function with one template argument it works. But why doesn't it work for the function with two template arguments? I always get the error
'f' : too many template argumentsI'm using VC++ 6.0 (SP3). I hope you can help me get around the error.
Regards Elena Günzler
A
I thought I stopped working for Microsoft's Developer Support in 1992, but sometimes I wonder if leaving was just a dream...
Your sample reduces to
template<typename T1> void f(T1) { } template<typename T1, typename T2> void f(T1, T2) { } int main() { int i(0); long l(0); f<int const &>(i); f<int const &, long const &> (i, l); return 0; }Compiling this with VC++ does indeed yield the error you cite. Translating with more conforming systems like EDG or CodeWarrior gives no errors.
VC++ is wrong; this code should translate fine. I believe this behavior falls under the category of "known problem," meaning that Microsoft's compiler team is aware of the bug.
A simple solution: change one of the template's qualified names, either by moving it into a namespace, or by changing its base name from f to something else. However, based on parts of your email that I've omitted here for brevity, I'm assuming you won't want this solution.
From your example, I'm also assuming that you want to instantiate the templates with reference arguments (as I've shown here). If that's always the case, you can use partial specialization:
template<typename T1> void f(T1 const &) { } template<typename T1, typename T2> void f(T1 const &, T2 const &) { } int main() { int i(0); long l(0); f(i); f(i, l); return 0; }Now both f versions implicitly bind their arguments to reference parameters. As a result, you no longer need the explicit template arguments.
If you want some instantiations to generate reference parameters, and others to generate non-reference parameters, you might be tempted to cast your function arguments:
template<typename T1> void f(T1) { } template<typename T1, typename T2> void f(T1, T2) { } int main() { int i(0); long l(0); f((int const &) i); f((int const &) i, (long const &) l); return 0; }under the notion that such casts will force implicit instantiations with reference parameters. Unfortunately, my interpretation of the C++ Standard suggests that reference arguments convert to non-reference expressions before type-matching occurs [5]. As a result, the templates are instantiated with non-reference parameters.
More Mousetraps
Q
In the July 1999 issue of CUJ, under the title "Better Mousetrap," you show Carlo Pescio's version of a lengthof template using the size of a struct.
I can't find the relevant text in the C++ Standard, but if I recall correctly, an implementation is allowed to pad a struct to a suitable alignment. That means that, given
struct three_chars { char x[3]; };the expression
sizeof (struct three_chars)may return 4 on an architecture that requires four-byte alignment of structs. This means that Mr. Pescio's implementation may not be portable; although I admit that I haven't yet found such a platform. Branko Cibej
A
Several Diligent Readers made this same observation. I've camped out on this question for a while, because I wanted time to scour the Standards for an answer. My verdict: your allegation is correct.
At first I thought justification came from C++ Standard subclause 9.2 ("Class members"):
A pointer to a POD-struct object, suitably converted using a reinterpret_cast, points to its initial member (or if that member is a bit-field, then to the unit in which it resides) and vice versa. [Note: There might therefore be unnamed padding within a POD-struct object, but not at its beginning, as necessary to achieve appropriate alignment.]
Overlooking the fact that the Standard's notes aren't normative, I wasn't sure how to interpret "within." The word implies to me that there is something on both sides of the padding, so that the padding is "within" the gap. As three_chars has only one member (x), there is no gap to be "within." I admit that my interpretation may be wrong here [6].
I find more compelling evidence in subclause 5.3.3 ("Sizeof"):
When applied to a class, the result is the number of bytes in an object of that class including any padding required for placing objects of that type in an array.
When applied to an array, the result is the total number of bytes in the array. This implies that the size of an array of n elements is n times the size of an element.
I read this to mean that:
sizeof(three_chars::x)is always 3 (remember, sizeof(char) == 1), while
sizeof(three_chars)is at least 3, but may be more if an array of three_chars requires padding between elements.
(The C Standard features similar language, including that same ambiguous usage of "within" to describe intra-structure padding.)
Like you, I don't know of an implementation for which sizeof(three_chars) > 3. I tried playing with #pragmas and other settings on the translators I have at home, but could never get a result other than 3. I invite Diligent Readers with who get a size > 3 to let me know.
Erratica
In this month's column I discuss the lack of proper exception specification support in VC++. I touched on this issue in my January 2000 column item "Deconstruction." At that time I interpreted the behavior as a bug in std::unexpected. Since then I've discovered that the real flaw is in VC++ exception specifications. (VC++ may have flaws in std::unexpected as well, but until the exception specifications work, std::unexpected can't be triggered.)
Diligent Readers Scott Neugroschl and Michael Taylor responded to that column, alerting me to the true nature of exception specifications in VC++. Even though I had already found the truth on my own, I want to thank them for trying to set me straight.
Notes
[1] Uncaught exceptions percolating outside main induce a call to std::terminate. That function, amazingly enough, terminates its host program.
[2] VC++ doesn't even accept the syntax of function try blocks.
[3] C++ Standard subclauses 15.5.2 and 18.6.2.
[4] VC++ accepts empty exception specifications without comment, but flags non-empty specifications such as throw(int) with a warning.
[5] This nuance came up in my September 1999 column, as part of the item titled "It Slices, It Dices!."
[6] By dissecting the subtle meanings of individual words in the Standard, I'm engaged in what Dan Saks calls the Talmudic Scholarship of C++.
Bobby Schmidt is a freelance writer, teacher, consultant, and programmer. He is also an alumnus of Microsoft, a speaker at the Software Development and Embedded Systems Conferences, and an original "associate" of (Dan) Saks & Associates. In other career incarnations, Bobby has been a pool hall operator, radio DJ, private investigator, and astronomer. You may summon him on the Internet via BobbySchmidt@mac.com.