home *** CD-ROM | disk | FTP | other *** search
- Copyright (C) 1992 MetaWare Incorporated. All Rights Reserved.
-
- High C/C++ Version 3.0
- Globally Optimizing Compiler
- for Extended DOS 80386/486 and Windows 3.0
- April 1992
-
- +------------------------+
- | Notes About High C++ |
- +------------------------+
-
-
- Table of Contents
- -----------------
- 1. Pointer-to-member comparison
- 2. Using templates
- 2.1 Ways to control the single-copy problem
- 2.2 Class templates
- 2.3 Checking template usage
- 2.4 A note on terminology
- 2.5 Problems with and possible future directions for C++ templates
-
-
- 1. Pointer-to-member comparison
- ===============================
-
- When the pointed-to member function is a virtual function, a comparison
- between two pointer-to-member-functions may not yield the results you expect.
- If one source file sets the value and a different source file does the
- comparison, the comparison fails because different function "thunks" are
- used to implement the call to the virtual function. For example:
-
- hdr.h: struct s { virtual foo(); };
-
- 1.cpp: #include "hdr.h"
- extern void (s::*p)(); extern sub();
- main () {
- sub(); // Set the pointer.
- if (p == s::foo) printf("OK\n"); // Will not print OK.
- }
-
- 2.cpp: #include "hdr.h"
- void (s::*p)();
- sub() { p = s::foo; }
-
- To get this to work requires either modifying linkers to support FORTRAN
- common-block style of code segments, or increasing the size of the
- representation of pointer-to-member-functions, neither of which seems
- especially desirable.
-
-
- 2. Using templates
- ===================
-
- Templates are a powerful extension to C++. Templates allow you to define
- generic functions or classes. Unlike macros, which are sometimes used to do
- the same thing (for example, generic.h), instantiations of templates are
- accompanied by full type checking.
-
- However, there is a problem with using templates: you must ensure that for
- each distinct set of parameters to a template, there is only a single copy
- of an instantiated function or static class data member.
-
- We have some reservations about methods we have seen for handling the single-
- copy problem automically. One way, used by cfront 3.0, is to maintain a
- database of template instantiations and corresponding object code; we are
- concerned about the complexity and overhead of this approach. Another
- approach, used by Borland C++, is to make proprietary extensions to the
- object-module format to support so-called "virtual" code and data segments,
- and to use a special linker. We are concerned about the usability of High C++
- in multiple environments with an extension such as this.
-
- So, for now, manual control over the single-copy problem is required. The
- default operational mode of the compiler is to always generate instances of
- function templates and classes when they are used in a source module -- one
- instance in the source module is generated per set of parameters to the
- template.
-
- The long and short of it is that High C++ currently requires the programmer to
- exercise manual control over this problem. We show recommended ways of doing
- it here.
-
-
- 2.1 Ways to control the single-copy problem
- --------------------------------------------
-
- If you are using templates defined locally within a single source module,
- there is no single-copy problem and no special care is needed. Each source
- module has its own templates and instances:
-
- 1.cpp: template <class T> T min(T x, T y) {...}
- . . .
- int i = min(1,2) + min(3,4);
-
- 2.cpp: template <class T> T max(T x, T y) {...}
- . . .
- int j = max(1,2) + max(3,4);
-
- Here each module contains an instance of a template function: min for 1.c and
- max for 2.c. The problem arises only when you are using the same template in
- separate source modules:
-
- hdr.h: template < class T > T min(T x, T y) {...}
- template < class T > T max(T x, T y) {...}
-
- 1.cpp: #include "hdr.h"
- int i = min(1,2) + max(3,4);
-
- 2.cpp: #include "hdr.h"
- int j = min(1,2) + max(3,4);
-
- Here the object modules for 1.cpp and 2.cpp both contain definitions of
- min(int,int) and max(int,int), and the linker will complain about a duplicate
- definition.
-
- The way to prevent duplicate definitions is to turn off automatic template
- instantiations when compiling. You turn them off for template functions by
- turning off the toggle Auto_func_instantiation:
-
- 1.cpp: pragma off(Auto_func_instantiation);
- #include "hdr.h"
- int i = min(1,2) + max(3,4);
-
- 2.cpp: pragma off(Auto_func_instantiation);
- #include "hdr.h"
- int j = min(1,2) + max(3,4);
-
- The toggle must be turned off in the presence of the function template
- definition -- not the calls to the function template. Essentially, the
- compiler records the setting of the toggle when it encounters the function
- template. For a non-inline function template encountered with the toggle off,
- there are no instantiations of the function template bodies in the source
- code.
-
- But now you have two modules, both of which make external references to
- min(int,int) and max(int,int), and the linker will complain of missing
- functions.
-
- You can provide the necessary functions as follows: in a single new source
- module, include calls to the function templates with the appropriate
- arguments, and keep the Auto_func_instantiations toggle on. The object code
- for this module contains the required instantiated functions.
-
- We recommend you place these calls in the module in such a way that the
- functions are never actually called at run time, and the code for calling them
- is never actually generated by the compiler. Here is a convenient way to do
- this with macros:
-
- fti.cpp: #include "hdr.h"
- #define DONTCALL(f) (0 && f)
- static void dummy() {
- DONTCALL(min(1,1));
- DONTCALL(max(1,1));
- };
-
- Because of the conditional in the macro, the body for dummy() is empty. Yet
- the object module for this source module contains the definitions of
- min(int,int) and max(int,int).
-
-
- 2.2 Class templates
- --------------------
-
- The examples above centered on function templates, but similar considerations
- apply to class templates. A class template must always be instantiated when
- it is used -- since the resultant type is required for further processing --
- but the single-copy problem applies to the non-inline class member functions
- and static data members. Again, if you use a class template only locally to a
- source module there is no problem; sharing an instantiation of a member
- function or data member is the problem.
-
- As with function templates, the default mode of the compiler is to always
- generate instances of class members when the class is used in a source module,
- and when the definitions of those members is likewise presented to the
- compiler (you could omit the definitions if you wanted). You can turn off
- this automatic instantiation by turning off toggle
- Auto_class_member_instantiation. Then you must provide another source module
- with the toggle on, in which you define the class members. For example:
-
- stack.h: template < class T > class stack {
- T *v, *p; int sz;
- public:
- stack(int);
- ~stack() { delete[] v; }
- void push(T);
- T pop() { return *--p; }
- };
- template<class T> void stack<T>::push(T a) { *p++ = a; }
- template<class T> stack<T>::stack(int s) {
- v = p = new T[sz=s];
- }
-
- 3.cpp: pragma Off(Auto_class_member_instantiation);
- #include "stack.h"
- f() {
- stack<int> I(10); stack<float> F(20);
- I.push(1); F.push(1.0);
- I.pop(); F.pop();
- }
-
- 4.cpp: pragma Off(Auto_class_member_instantiation);
- #include "stack.h"
- f() {
- stack<int> I(10); stack<float> F(20);
- I.push(2); F.push(2.0);
- I.pop(); F.pop();
- }
-
- Here, both modules contain external references to stack<int>::push(int),
- stack<int>::stack(int), stack<float>::push(float), and
- stack<float>::stack(float). Note that there are no references to the inline
- members (such as pop) of stack<int> and stack<float>; these inline members are
- in fact generated by the compiler and inlined. The external references are
- made only to the non-inline functions and static data members.
-
- You can provide the push and constructor functions by compiling the following
- module that simply refers to the two stack types:
-
- cti.cpp: #include "stack.h"
- typedef stack<int> dummy1;
- typedef stack<float> dummy2;
-
-
- 2.3 Checking template usage
- ----------------------------
-
- You can get an idea of which templates were used in a compilation by turning
- on toggle Print_template_usage. Both function templates and class templates
- are printed and, for a class template, its members are printed indented below
- the class template. There is a single character in the leftmost column:
-
- I -- means the compiler instantiated this.
- e -- means undefined (it's external) and you must supply it.
- U -- means the user defined a special version of a function, class
- member, or class template.
-
- For example, the result from compiling 3.cpp above is:
-
- Template usage:
- I stack<int> (at "3.cpp",L5/C7)
- e stack(int) (at "stack.h",L4/C11)
- e void push(int) (at "stack.h",L6/C16)
- I stack<float> (at "3.cpp",L5/C25)
- e stack(int) (at "stack.h",L4/C11)
- e void push(float) (at "stack.h",L6/C16)
-
- Here we see that the two stack classes are instantiated (I) but the two member
- functions are external (e). Compiling cti.cpp you see them instantiated:
-
- Template usage:
- I stack<int> (at "cti.cpp",L2/C11)
- I stack(int s) (at "stack.h",L10/C26)
- I void push(int a) (at "stack.h",L9/C26)
- I stack<float> (at "cti.cpp",L3/C11)
- I stack(int s) (at "stack.h",L10/C26)
- I void push(float a) (at "stack.h",L9/C26)
-
-
- 2.4 A note on terminology
- --------------------------
-
- The ARM talks about "function templates" and "template functions". A template
- function is an instantiation of a function template. Be aware of this
- distinction when reading the ARM. We think this terminology is unfortunate;
- the two phrases are too close and are easily confused. We've seen it happen
- repeatedly at ANSI C++ standards meetings. So we use "instantiation" or
- "instance" rather than "template function", and will move to make a similar
- change in the C++ draft standard.
-
- "class templates" and "template classes" are similarly related, and we vote
- for a similar change in terminology.
-
- 2.5 Problems with and possible future directions for C++ templates
- -------------------------------------------------------------------
-
- Instantiating a function template requires an exact match of the function
- formal-parameter types and the actual-parameter types. This can be somewhat
- of a nuisance. For example, suppose you generalize the ANSI C bsearch()
- routine with a template:
-
- template <class T> T *bsearch(const T key, const T *base, int nmemb);
-
- Because you've used const, your generic search routine "promises" never to
- clobber the elements being searched, nor the key. This is a valuable promise.
- But when you try to call bsearch():
-
- extern int a[10];
- int *solution = bsearch(123, a, 10); // (A)
-
- the call fails, because 123 is not a const, and a is not of type pointer-to-
- const. In fact, you have to write:
-
- extern int a[10];
- int *solution = bsearch((const int)123, (const int *)a, 10);
-
- One way to alleviate this problem is to define an additional inline template
- that allows non-const parameters, but which calls the "real" one:
-
- template <class T> inline T *bsearch(T key, T *base, int nmemb) {
- bsearch((const T)key, (const T*)base, nmemb);
- }
-
- This would allow the call (A) to work; the second template is called, which
- turns around and calls the first. Since the second is inline, there is no
- performance penalty.
-
- There have been requests to loosen up the exact-match rule to permit a non-
- const parameter to "matched" with a const parameter in a function template.
- Turning on the High C++ toggle Template_trivial_conversions permits so-called
- trivial conversions (in the parlance of the ARM) to be allowed when matching
- arguments to function templates. This allows call (A) to work without
- introducing the intermediate inline template.