Newsgroups: comp.lang.c++ Path: utzoo!utgpu!craig From: craig@gpu.utcs.utoronto.ca (Craig Hubley) Subject: overloading inconsistent? (Re: Co-ordinating the polymorphism in C++) Message-ID: <1991Feb19.051123.5198@gpu.utcs.utoronto.ca> Followup-To: comp.std.c++ Organization: UTCS Public Access References: <1991Feb11.003849.27340@gpu.utcs.utoronto.ca> <600@taumet.com> <1991Feb13.011731.10114@gpu.utcs.utoronto.ca> <27BFD8E3.3D1D@tct.uucp> Date: Tue, 19 Feb 1991 05:11:23 GMT In article <27BFD8E3.3D1D@tct.uucp> chip@tct.uucp (Chip Salzenberg) writes: >According to craig@gpu.utcs.utoronto.ca (Craig Hubley): >>Overloading *is* a form of polymorphism, albeit a static one. > >I agree. OK, so we agree that overloading is static. :) >>This is no different that building three types that inherit the same >>virtual function. > >I disagree. Overloading is a static phenomenon limited to compile >time, and that fact is visible to the programmer. For example, given I plead guilty of posting to the wrong group: arguments about what C++ SHOULD do belong in comp.std.c++, not here. But to answer, these are no different in *principle*. Annoyingly different in expression in C++, and (I find) often intolerable in practice. The fact that C++ implements the two mechanisms so as to create (in my opinion) counter-intuitive effects is (in my opinion) a flaw in C++. That is, the programmer must be very much aware of the "seam" between compile-time and run-time, and cannot simply present a set of choices to the system, some of which can be made at compile-time and others at run-time as the information is available. It is that visibility to the programmer that is so disturbing. Consider how static initialization works in C++. The initialization syntax means that some functions consist entirely of initialization and calls to these can (in principle) be resolved entirely at compile- time if they are called with static arguments. That same function can be used at run-time, and the programmer need not be aware of the difference. This is very different than in C, which has no initialization syntax, and forces the system to wait until run-time to resolve assignments. It is a major advantage over C to be able to build one recursive data- structure-building function and call it with static arguments. In the bad old C tradition, we would have been forced to build a set of initializations or data file that would have had to be maintained separately from the functions that add to the structure at run-time. Or we could have bit the bullet and waited 10 min. to initialize stuff at runtime, every time we fired up our application. Point being, it *is* possible to have one mechanism that is resolved at both compile-time and run-time. >a base class B and a derived class D: > > extern void foo(B&); > extern void foo(D&); > D d; > B& b = d; > foo(b); > >It is the "foo(B&)" function that will be called, the decision being >based on the _static_ type of the argument. Likewise, given a >non-virtual member function "foo()" defined in both B and D [no pun >intended!]: > > D d; > B& b = d; > b.foo(); > >It is B::foo() that is called, this decision being made in a way very >similar to the resolution of overloaded functions. Yes, but it is the interaction of virtual functions and overloading that presents the interesting problems. In neither of the cases above can the system be expected to read the programmer's mind and "decide" to call the function associated with the "real" rather than the "formal" type. In fact, C++ explicitly uses the -> vs. . notation so that the programmer can make that decision him/herself. In your latter example, you are *telling* the system to use the formal type. In the former, however, there is *no way* to tell the system to use the real type. That is, there is no "cast to the dynamic type" operator in C++ that would delay this decision until run-time. You can't even find out what the type is. >In contrast, virtual function resolution is a _dynamic_ phenomenon. >Were foo() made virtual, however, the decision between B::foo() or >D::foo() -- or even some other, as yet unimagined, function E::foo() >-- would be deferred until run time. > >So I conclude that it is NON-virtual member function resolution that >correspond most closely to function overloading. You have a good analogy there, but what you are proving is that overloading is solving the same problem as member functions, but without the other half of its brain: runtime resolution. When you mix virtuals and overloading, things get scary, and in my opinion it's a direct result of this failing. There is already a posting describing that in detail, so I won't repeat it. >>>Templates and polymorphism are orthogonal concepts. >> >>Templates are closely related to inheritance in that they are both >>ways to create a family of types with similar behavior. > >The preprocessor is also a means to creating a class family; yet it is >obviously unrelated to inheritance per se. As I understand templates, It is just a way of building templates with macros, and is obviously related in the way that I describe: you can use it to create a family of types with similar behavior. >it is true that classes may be defined in template form, but so may >anything, including non-member functions that could otherwise be part >of an old C program, e.g. a generic qsort() comparison function. So >templates and inheritance _are_ truly orthogonal. You can use templates to do things that have nothing to do with inheritance, they are in fact an *alternate* way to do some things. For example, create a "protected" or "persistent" class without using a mixin. Fair enough. But if "orthogonal" in this sense is supposed to mean "never interact at all" you are wrong. Consider a class that I make with a template "Persistent". Call it "PersistentEmployee". Now imagine I make another one: "PersistentManager". If Manager : public Employee, does PersistentManager: public PersistentEmployee ? Regardless of which way language policy goes, you still have to make the decision. Deciding "no, that's the job of inheritance" doesn't solve the problem, it just ignores it. And both are providing forms of polymorphism, which you could call "type ignorance" for the user. C++ templates are examples of what is often called "parametrized polymorphism", whereas inheritance provides "hierarchical polymorphism". Templates, by the way, don't suffer from the problem discussed above: Although you could build this kind of type at compile-time using the preprocessor before templates were added, you couldn't have extended that syntax to deal with types to be determined at run time. The template syntax would permit this, were it to be added to C++. >>"Orthogonal" in the sense that they can be implemented separately, but >>not in terms of how they interact to the user. > >Again, though, templates are a purely static phenomenon. They are The quick answer is "who cares?". As a programmer, I couldn't care less at what point in the compile/link/load/run process these things are resolved, except where the language forces me to be aware of it. Which in C++ is annoyingly often. I am not *misunderstanding* how this works, I am *disagreeing* with it. Again, I apologize for not having started this in comp.std.c++. Followups redirected to there. >utterly unlike virtual functions, which are resolved at run time. Your sense of "utterly unlike" seems to be that of a compiler designer, which probably represents .0001% of C++ users, and dropping rapidly. Obviously you have to be aware of these subtle differences sometimes but inventing wholly different syntax for each situation is IMHO a mistake. And the situation could be improved by letting overloading, virtuals, and builtin conversion interact consistent with the object-oriented paradigm. Which, at present, they don't. I suppose the problem starts with Bjarne who wrote his first book for compiler writers not programmers. In *most* languages, if one includes the many commercial 4GLs, compile-time/run-time distinctions are hidden from the programmer. Similarly for the OOPLs. True OOPLs tend to treat everything as if it were potentially to be resolved at runtime, but then prunes the possibility tree at compile-time. In other words, true OOPLs have optimizing compilers, not inconsistent compile vs. run-time syntax. But that is a theological matter. :) >Chip Salzenberg at Teltronics/TCT , -- Craig Hubley "...get rid of a man as soon as he thinks himself an expert." Craig Hubley & Associates------------------------------------Henry Ford Sr. craig@gpu.utcs.Utoronto.CA UUNET!utai!utgpu!craig craig@utorgpu.BITNET craig@gpu.utcs.toronto.EDU {allegra,bnr-vpa,decvax}!utcsri!utgpu!craig