Previous Table of Contents Next


Some conclusions about modularity and how a program is composed out of separately compiled parts were explicitly reflected in the original C++ reference manual (Stroustrup, 1984a):

  Names are private unless they are explicitly declared public.
  Names are local to their file unless explicitly exported from it.
  Static type is checked unless explicitly suppressed.
  A class is a scope (implying that classes nest properly).

The first point doesn’t affect C compatibility, but the other three imply incompatibilities:

  The name of a non-local C function or object is by default accessible from other compilation units.
  C functions need not be declared before use and by default calls are not type checked.
  C structure names don’t nest (even when they are lexically nested).
  C++ has a single namespace whereas C had a separate namespace for “structure tags” (section 6.2.4.5).

The “compatibility wars” now seem petty and boring, but some of the underlying issues are still unresolved and caused problems for the ANSI/ISO committee. I strongly suspect that the reason the compatibility wars were drawn out and curiously inconclusive was that we never quite faced the deeper issues related to the differing goals of C and C++. Instead we saw compatibility as a set of separate issues to be resolved individually.

Typically, the least fundamental issue, namespaces, took the most effort but was eventually resolved (Ellis & Stroustrup, 1990).

I had to compromise the notion of a class as a scope and accept the C “solution” in order to be allowed to ship Release 1.0. One practical problem was that I had never realized that a C struct didn’t constitute a scope so that examples like this are legal C:

   struct outer {
       struct inner {
           int i;
       };
       int j;
   };

   struct inner a = { 1 };

When the issue came up toward the end of the compatibility wars, I didn’t have time to fathom the implications of the C solution, and it was much easier to agree than to fight the issue. Later, after many technical problems and much discontent from users, nested class scopes were re-introduced into C++ in 1989 (Ellis & Stroustrup, 1990).

After much hassle, C++’s stronger type checking of function calls was accepted (unmodified). An implicit violation of the static type system is the original example of a C/C++ incompatibility that is not gratuitous. As it happens, the ANSI C committee adopted a slightly weaker version of C++’s rules and notation on this point and declared uses that don’t conform to the C++ rules obsolete.

I had to accept the C rule that global names are by default accessible from other compilation units. There simply wasn’t any support for the more restrictive C++ rule. This meant that C++, like C, lacked an effective mechanism for expressing modularity above the level of the class and the file. This led to a series of complaints, and in 1994, the C++ standard committee accepted a proposal for a mechanism to avoid namespace pollution (section 6.5.7) (Stroustrup, 1994). However, people—such as Doug McIlroy—who argued that C programmers would not accept a language in which every object and function meant to be accessible from another compilation unit had to be explicitly declared as such, were probably right at the time and saved me from making a serious mistake. I am now convinced that the original C++ solution wasn’t elegant enough anyway.

6.3.5. Tools for Language Design

Theory and tools more advanced than a blackboard have not been given much space in the description of the history of C++. I tried to use yacc (an LALR(1) parser generator) for the grammar work and was defeated by C’s syntax (section 6.2.4.5). I looked at denotational semantics but was again defeated by quirks in C. Ravi Sethi had looked into that problem and found that he couldn’t express the C semantics that way (Sethi, 1980). The main problem was the irregularity of C and the number of implementation-dependent and undefined aspects of a C implementation. Much later, the ANSI/ISO C++ committee had a stream of formal definition experts explain their techniques and tools and give their opinion of the extent to which a genuine formal approach to the definition of C++ would help us in the standards effort. My conclusion is that with the current state of the art, and certainly with the state of the art in the early 1980s, a formal definition of a language that is not designed together with a formal definition method is beyond the ability of all but a handful of experts in formal definition.

This confirms my conclusion at the time. However, that left us at the mercy of imprecise and insufficient terminology. Given that, what could I do to compensate? I tried to reason about new features both on my own and with others to check my logic. However, I soon developed a healthy disrespect for arguments (definitely including my own) because I found that it is possible to construct a plausible logical argument for just about any feature. On the other hand, you simply don’t get a useful language by accepting every feature that makes life better for someone. There are far too many reasonable features and no language could provide them all and stay coherent. Consequently, wherever possible, I tried to experiment.

My impression was and is that many programming languages and tools represent solutions looking for problems, and I was determined that my work should not fall into that category. Thus, I follow the literature on programming language and the debates about programming languages, primarily looking for ideas for solutions to problems my colleagues and I have encountered in real applications. Other programming languages constitute a mountain of ideas and inspiration—but it has to be mined carefully to avoid featurism and inconsistencies. The main sources for ideas for C++ were Simula, Algol68, and later Clu, Ada, and ML. The key to good design is insight into problems, not the provision of the most advanced features.


Previous Table of Contents Next