Previous | Table of Contents | Next |
6.5.4.3. Declarations in Conditions
Where people conscientiously try to avoid uninitialized variables, they are left with one of the following:
int i; cin>>i;
Tok* ct; if (ct = gettok()) { /* ... */ }
During the design of the runtime type identification mechanism in 1991 (section 6.5.8), I realized that the latter cause of uninitialized variables could be eliminated by allowing declarations to be used as conditions:
if (Tok* ct = gettok()) { // ct is in scope here } // ct is not in scope here
This feature is not merely a cute trick to save typing. It is a direct consequence of the ideal of locality. By joining the declaration of a variable, its initialization, and the test on the result of that initialization, we achieve a compactness of expression that helps eliminate errors arising from variables being used before they are initialized. By limiting their scope to the statement controlled by the condition, we also eliminate the problem of variables being reused for other purposes or accidentally used after they were supposed to have outlived their usefulness. This eliminated a further minor source of errors.
The inspiration for allowing declarations in expressions came from expression languagesin particular from Algol68. I remembered that Algol68 declarations yielded values and based my design on that. Later, I found my memory had failed me: Declarations are one of the very few constructs in Algol68 that do not yield values! I asked Charles Lindsey about this and received the answer, Even Algol68 has a few blemishes where it isnt completely orthogonal. I guess this just proves that a language doesnt have to live up to its own ideals to provide inspiration.
If I were to design a language from scratch, I would follow the Algol68 path and make every statement and declaration an expression that yields a value. I would probably also ban uninitialized variables and abandon the idea of declaring more than one name in a declaration. However, these ideas are clearly far beyond what would be acceptable for C++.
In the original design of C++, parameterized types (templates) were considered but postponed because there wasnt time to do a thorough job of exploring the design and implementation issues. I first presented templates at the 1988 USENIX C++ conference in Denver (Stroustrup, 1988a):
For many people, the largest single problem using C++ is the lack of an extensive standard library. A major problem in producing such a library is that C++ does not provide a sufficiently general facility for defining container classes such as lists, vectors, and associative arrays.
There are two approaches for providing such classes/types: You can either rely on dynamic typing and inheritance as Smalltalk does, or you can rely on static typing and a facility for arguments of type type. The former is very flexible but carries a high runtime cost and, more importantly, defies attempts to use static type checking to catch interface errors. I chose the most flexible variant of the latter approach that I could devise. It was an important design aim for templates to be able to use them to define vector and list types that could compete with the built-in, but error-prone, array type. This implied that it had to be possible to perform operations on types specified using templates without function call overhead. Furthermore, it had to be possible to specify templates that put no restrictions on their parameters.
A C++ parameterized type is called a class template. A class template specifies how individual classes can be constructed much like the way a class specifies how individual objects can be constructed. A vector class template might be declared like this:
template<class T> class vector { T* v; int sz; public: vector(int); T& operator[](int); T& elem(int i) { return v[i]; } // ... };
The template<class T> prefix specifies that a template is being declared and that an argument T of type I will be used in the declaration. After its introduction, T is used exactly like other type names within the scope of the template declaration. Vectors can then be used like this:
vector<int> v1(20); vector<complex> v2(30); typedef vector<complex> cvec; // make cvec a synonym for vector<complex> cvec v3(40); // v2 and v3 are of the same type v1[3] = 7; v2[3] = v3.elem(4) = complex(7,8);
C++ does not require the user to explicitly instantiate (that is, specify which version of a template needs to be generated for particular sets of template arguments) a template. The most fundamental reason is that only when the program is complete can it be known what templates need to be instantiated. Many templates will be defined in libraries and many instantiations will be directly and indirectly caused by users who dont even know of the existence of those templates. It therefore seemed unreasonable to require the user to request instantiations (say, by using something such as Adas new operator). Requiring explicit instantiation of template functions would also have seriously hampered generic programming.
Avoiding unnecessary space overheads caused by too many instantiations of template functions was considered a first orderthat is, language-levelproblem rather than an implementation detail. I considered it unlikely that early (or even late) implementations would be able to look at instantiations of a class for different template arguments and deduce that all or part of the instantiated code could be shared. The solution to this problem was to use the derived class mechanism to ensure code sharing among derived template instances.
The template mechanism is completely a compile-time and link-time mechanism. No part of the template mechanism needs runtime support. This leaves the problem of how to get the classes and functions generated (instantiated) from templates to depend on information known only at runtime. The answer was, as ever in C++, to use virtual functions and abstract classes. Abstract classes used in connection with templates also have the effect of providing better information hiding and better separation of programs into independently compiled units.
Previous | Table of Contents | Next |