C/C++ Contributing Editor


Uncaught Exceptions: comp.cuj.qa.moderated

Bobby Schmidt

Never one to let his uncertainty show, Bobby nevertheless falls prey to Heisenberg.


Copyright © 2000 Robert H. Schmidt

Lately I've received submissions like

All of these fall outside my charter, which is to answer questions about Standard C and C++. Fair game for this column: anything you would legitimately post to the newsgroups

In addition I make sorties into closely related topics such as compiler bugs and non-conformance. I almost never answer questions specific to a particular host or target platform, or that incorporate non-Standard language and library extensions. (Apparent exception: the plethora of VC++ questions, although they really fall into the bucket of compiler bug/non-conformance.)

I'd also like to remind one and all that, as much as I'd like to, I really can't give free consulting advice through this column. Many of the questions I get asked are quite interesting, but would require too much time to research and answer on the cheap [1].

On a related note: I don't mind you sending me code, but please try to make your samples the smallest possible, just enough to demonstrate your problem or question. Not only does it take me longer to work through long examples, but I try not to tie up too much column space with those examples.

Finally, I want to thank you all for the interest you've shown in the magazine and in this column. For every question I can answer (either in print or via email), I have a dozen that I simply can't get to. As I've mentioned in the past, if you need a quick turnaround, I suggest you patronize the newsgroups I listed above. At the same time, I want to encourage you to keep sending me those questions. The more variety you give me, the more opportunity I have for wit and entertainment and, oh all right, education too.

Turtology

Q

"Turtles all the way down" I understand, but I think the Heisenberg analogy is ill-founded. Apparently the sentence in your November 1999 CUJ column, which is supposed to justify the Heisenberg reference is the following:

If the act of defining an object changes the object type's size, that type is clearly not yet complete.

Despite the verbal echoes of Heisenberg, this does not really follow the pattern of the Uncertainty Principle, where the act of observing an object changes the object's own behavior but not the size or definition of the object's type. Bouncing a light beam off an electron to observe it will knock the electron off course, but it won't change the size or constant physical characteristics of all the electrons in the universe.

For truly Heisenberg-like behavior (which may be of no real-life programming use whatever), consider the following partially defined class:

class H
   {
private:
   int n;
public:
   // ...
   operator int()
      {
      return rnd() %2 ? n++ : n—;
      }
   };

Here any attempt to get back the actual contents of an H object will leave the object in an altered state; if int(h) returns 7, we know that h.n used to be 7 and is now either 6 or 8, but we don't know which and can't find out without disturbing it again. — Larry Kuenning

A

I started my undergraduate life as a Biology major. During my sophomore year I discovered that, while I was interested in biology and other "hard" sciences, I wasn't interested enough to make a career of them. (I think flagging Organic Chemistry helped clarify my discovery.) At the end of that year, I switched my major to Computer Science, which I found much more intellectually and esthetically satisfying.

In my CS curricula, the classes that gave me the most trouble were Physics II and III — that is, pretty much everything beyond the blocks-sliding-down-a-plane stage. Of the physics topics that squashed me, quantum mechanics among them, I learned just enough to be dangerous — or at least, in later life, to make dangerous analogies.

So yes, after reading your letter, I realize that my editorial reach somewhat exceeded my grasp. But I couldn't have missed the mark too badly, for you did see what I was aiming for, even if I didn't hit it spot on. And that, in the end, is what I was really after [2].

Linka-Dinka-Do

Q

Dear Mister Schmidt,

I hope all is going well for you in cyberspace as well as real space. Furthermore, the happiest of holidays to you. You at the CUJ have always been pretty generous about freebies and general dispensing of knowledge. As well as being genuinely appreciative of this, I'm coming back to the well once again...

Whether or not this merits an entry in your column I don't know, but we've been encountering a strange compiler warning (Sun Sparcworks C++ compiler, 5.0). The code synopsis is this:

extern "C" void func(void (*)());

class Tclass
   {
public:
   Tclass ()
      {
      }
  static void static_func()
      {
      }
  void a ()
      {
      func(static_func); // WARNING
      }
   };

int main()
   {
   Tclass T;
   }

The compiler generates this on the line indicated:

Warning (Anachronism): Formal
argument 1 of type extern "C"
void(*)() in call to
func(extern "C" void(*)()) is being
passed void(*)().

Weird (but not a real problem). Any thoughts? Once again, thanks for sharing your smarts. — Sincerely, Bill Palladino

A

Before launching into the boring technical answer, I want to thank you for your lovely and kind words. We here at CUJ certainly strive to make your Q&A experience a pleasant one. We aren't happy until you're happy. Customer service is our life purpose...z-z-z-Z-Z-Z...

Well, you get the picture. However, I must correct one small misconception. What I dispense may be a "freebie" to you, but it certainly isn't to CUJ. Not that it's far from a freebie, given what they pay me; but if you carry out to enough decimal places, technically speaking I don't work for free.

Now for some of that bodacious customer service.

Your compiler's diagnostic is on the money. It tells you that the actual argument TClass::static_func is not the same type as the first formal argument (parameter) of func. The diagnostic label "Anachronism" suggests that the two types were the same in the hoary Old Days, but are no longer.

I've crafted a small example that I think more clearly shows the problem:

extern "C"   typedef void ((*C)  ());
extern "C++" typedef void ((*CPP)());

C   c   = 0;
CPP cpp = 0;

int main()
   {
   c   = cpp; // ERROR
   cpp = c;   // ERROR
   return 0;
   }

The two pointer-to-function definitions C and CPP are identical, with one exception: the former has C language linkage, while the latter has C++ language linkage. From the C++ Standard (7.5, "Linkage"):

All function types, function names, and variable names have a language linkage.

The default language linkage of all function types, function names, and variable names is C++ language linkage. Two function types with different language linkages are distinct types even if they are otherwise identical.

In my example, even though both C and CPP define void (*)() pointers, they are different types because their linkages are different. By implication, the objects c and cpp have different function-pointer types. Because C++ has no standard conversion between different function-pointer types, the two objects are not assignment-compatible, and the code shouldn't compile.

That's the theory anyway. In practice, every system I've ever used shares the same calling conventions for C and C++ functions. On these systems, treating a C function pointer as a C++ function pointer (or vice versa) causes no tangible problem. Many compilers therefore support a language extension allowing implicit conversion among C and C++ function pointers.

I don't have access to a Sparcworks compiler. Of the translators I currently have available to me, only EDG in strict mode gets it right:

a value of type "CPP" cannot be
assigned to an entity of type "C"
    c = cpp;
      ^

a value of type "C" cannot be
assigned to an entity of type "CPP"
    cpp = c;
        ^

You may wonder about my unusual declaration:

extern "C++" typedef void ((*CPP)());

Yes Virginia, there is such a thing as explicit C++ linkage. Don't panic if you've never seen this. Few people ever use it, since — as the above Standard excerpt teaches us — program entities default to C++ linkage.

Copyish Constructor

Q

Hi,

My question: to a copy constructor of a class we send the argument as a reference to the class. I can understand why we cannot pass by value (causes infinite recursion), but why can't we send a pointer to the class as the argument:

class X
   {
public:
   X(const X&);
   // why not X(const X*)?
   };

Thanks — Anita Ved

A

Nothing prevents you from defining an X constructor the way you want. Such a definition allows

X *p;
X x1(p);   // calls X(X const *)
X x2(&x1); // ditto

But if the copy constructor were defined this way, you could not write:

X x3(x1); // ERROR

or:

X x3 = x1; // ERROR

That is, you couldn't construct a new X object from an existing one. The closest you could come is:

X x3 = &x;

which doesn't look very natural.

Your constructor also would not allow:

void f(X);

f(x1); // ERROR

Again, the closest you could come is:

f(&x1);

Copy constructors allow a new object to be copied from an existing object, in a way that looks natural and C-like. They are required for many common C++ constructs to compile, especially those originally written in C. In addition, many template containers assume that their contained element type can copy itself (rather than copy a pointer to itself).

Slings and (Cupid's) Arrows

Q

Why do fools fall in love? — F. Lymon

A

Because they don't watch where they're walking.

Model Behavior

Q

What did I miss when I have a template class:

template <typename T>
RetStatus<T>

which works fine if implemented in the header file, but not if implemented in the source file? The error message after making:

/usr/ccs/bin/ld: Unsatisfied symbols:
RetStatus<int>::RetStatus( ... )%1
(code)
RetStatus<int>::~RetStatus()(code)
*** Error exit code 1

The first diagnostic was for the constructor, the second for the destructor. It seems that the compiler cannot see the implementation. Why?

Your insights are appreciated! — Zack Hu

A

What you've stumbled upon is a fork in the function template road. A full explanation would take several columns; I'll do my best to summarize here.

Templates, in and of themselves, do not produce code. As their name suggests, they are templates or frameworks for code, defining the pattern for a family of related classes and functions. When you instantiate a template, you create code having that template's pattern, customized for the template arguments you supply.

Assume for the sake of argument that you have a simple class template defined as:

template <typename T>
class X
   {
public:
   T f();
   };

Were you to translate a source file containing just the above, you would see no executable code produced. But once you instantiate the template:

X<int> x;

you direct the translator to produce a class definition matching the X pattern, with all the T's replaced by int. The effect is the same as physically writing out:

template <>
class X<int>
   {
public:
   int f();
   };

The translator creates a real class called X<int>, having a name, pattern, and meaning based on the template.

X<int> is a class type with a member function f. When your program makes the call:

x.f();

it needs some real function f to call into. Where does the body or definition of that f come from? Well, like the class definition, the function definition is instantiated from the template. That is, the translator takes the pattern defined for f, substitutes int for T, and produces a real function X<int>::f with a real body.

For the translator to instantiate a real f based on the f template pattern, the translator has to see the f pattern. This brings us to the fork in the road, for the translator really has two potential ways to see the f pattern:

In C++ lore, these two methods are called the inclusion model and the separation model. The inclusion model is the one you are familiar with and requires no extra language support. Conversely, the separation model requires the new Standard C++ keyword export:

export template <typename T>
class X
   {
public:
   T f();
   };

I've yet to see a translator actually support this keyword properly. While the separation model sounds lovely in theory, I suspect it is difficult for translator vendors to robustly implement in practice. As testament to that difficulty, every translator I've ever used employs the inclusion model.

The Standard talks about these compilation models in clause 14 ("Templates") paragraphs 6 through 9. Lippman and Lajoie also explain the models in their C++ Primer, pp. 509-12. In addition, they offer explicit template instantiation (pp. 512-514) as a way to mitigate some of the inclusion model's costs [3].

Erratica

The brand spanking new C Standard v2.0 (a.k.a C99) is slated to be published by February 2000. I still don't know how you can buy it, but at least it should exist somewhere in our shared space-time continuum (that is, outside the Committee's event horizon).

Speaking of black holes and all-consuming darkness, I can finally run Windows at home on my main Mac. Up until now, what little Windows I've run has been on a laptop P.J. Plauger generously lent me. But now I've finally purchased a PC emulator (Connectix Virtual PC) that runs on Mac OS. Since the "PC" runs as a Mac application, I end up running Windows in a window. Turtles all the way down, Heisenberg or otherwise [4].

This doesn't mean I'm about to start writing MSJ columns [Or what used to be MSJ — mb]. It does mean that I can better reproduce your scenarios, since most of you seem to run Windows-hosted compilers.

Notes

[1] Yeah, I could've been a Microsoft millionaire and spared myself all this agony, but I left it all behind for love. ("Why do fools...") Maybe someday when I'm desperate for column inches I'll tell the story.

[2] That, and the chance to coin a new C++ pattern buzz phrase. I felt I could hardly claim membership among the C++ Literati without naming a pattern, and all the really good names were taken. Even Scott Meyers has yet to make up a pattern, other than the Effective C++ book pattern.

[3] The primary costs: redundant definitions of the same instantiated function, which can slow translation and bloat executable file size; and exposure of the function implementation within the common header.

[4] Actually, I've come to believe that observing Windows does affect its behavior. I just wish the altered behavior could come in colors other than blue.

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 rschmidt@netcom.com.