From: ian@syacus.acus.oz.au (Ian Joyner)
Subject: C++ Critique (1 of 4) (long)
Summary: Philosophical, technical and pragmatic objections to C++, and why it sh
Keywords: C++, criticism
Organization: ACUS Australian Centre for Unisys Software, Sydney
Date: Wed, 8 Apr 1992 11:51:31 GMT

Apologies to those seeing this for a second time, as I found our default
distribution was not wide spread. A post script version is available on
request. Hopefully, this will open up sensible debate on the topic, and
such debate is long overdue and welcome.

			A C++ Critique
			by Ian Joyner

Note: This is a long submission. There is also a postscript version
available on request. Any reasonable comments appreciated.

Introduction	1
C++ Specific Criticisms	4
     Virtual Functions	4
     The Nature of Inheritance	7
     Name Overloading	8
     Function Overloading	9
     A Note on Overloading and Nesting	11
     A Note on "A Discipline of Programming"	14
     Pure Virtual Functions	15
     Virtual Classes	15
     '::"' '.' and'->'	16
     On information, computation and execution and paradigms	17
     Anonymous Parameters in Class Definitions	19
     Constructors	21
     Constructors and Templates	21
     Optional Parameters	22
     Bad Deletions	23
     Local Entity Declaration	23
     Friends	25
     Static	26
     Union	27
     Nested Classes	28
     Global Environments	29
     Header Files	29
     Class Header Declarations	31
     Calls on references to deallocated objects (Dangling Pointers)	32
     Type-safe Linkage	33
     C++ and the Software Lifecycle	34
     Reusability and Communication	35
     Reusability is a matter of Trust	35
     Concurrent Programming	36
The Role of Language	37
Generic C Criticisms	40
     Pointers	40
     Arrays	41
     Function Parameters	42
     void *	42
     void fn ()	43
     fn ()	43
     ++, --	45
     Defines	46
     Case Distinction	46
     Assignment Operator	48
     Type Casting	49
     Semicolons	50
Conclusions	52


                        Introduction
                        ============ 

This is a critique of the C++ language.  It is an attempt to address the
questions of whether C++ is a good OO language, can it be used to easily
implement quick and small projects, how well does it scale up for large
projects, and technical questions of how far it goes in supporting or hindering
general good programming practices, and as a result the production of quality
software? What is the relationship between a language and compiler, and software
developers; and between the language and compiler, and the target system?

A programming language functions at many different levels and roles, and must be
critiqued with respect to those levels and roles.  Not only should a language be
critiqued from a technical point of view, considering its syntactic and semantic
features, but it should be critiqued from the viewpoint of its contribution to
the entire software production process, how it facilitates communication between
levels, and how the communication of ideas may be facilitated from one
programmer to another, as often one programmer may not be responsible for a
whole task over its lifetime. 

The primary purpose of language is to facilitate communication, and a
programming language must facilitate the consistent description of a system
which satisfies the requirements of the problem at hand.  It should be able to
provide communication between programmers perhaps separated in space and time. 
A language should be able to express the intent of the systems designers and
implementers.  A language should also be able to provide mechanisms for project
tracking, to ensure that modules (classes and functionality) are completed in a
timely and economic fashion, and that the modules produced satisfy the original
requirements. 

A programming language aids reasoning about the design, implementation,
extension, correction, and optimisation of a system.  The preface to "The C++
Programming Language" has an interesting quote - "Language shapes the way we
think, and determines what we can think about." - B.L.Whorf.  Does language
shape the way we think? Does C++ shape the way we reason about programming? What
is the role of language and what is the role of a computer language? Fundamental
to the reusability of software is that subsequent programmers may be able to
understand the nature of that software, by means of the communication medium and
descriptive abilities of the programming language. 

Programming is a dynamic activity.  Program text usually does not stay static
for long, especially during the development stages.  Languages and compilers
should provide consistency checks that aid in this dynamic process, ensuring
that as program text is added or modified, previous work is not invalidated. 

That a compiler may be able to translate the program into an executable entity
is a secondary, and yet not unimportant consideration.  A compiler has a twofold
role.  Firstly, to generate code that the target machine may execute, but
secondly, and more importantly, to check that the programmers expression of the
system is indeed a consistent expression.  There is certainly not much point in
generating code for a system, where parts of the description of that system has
automatically detectable inconsistencies.  The language definition provides the
framework that makes this role of the compiler possible.  The task of the
machine is to execute the produced programs. 

Programming languages should not be the exclusive domain of programmers, but
should be tools in which design, and even requirements analysis may be achieved. 
Object-oriented techniques emphasise the importance of this, as abstract
definition, and concrete implementation are separated, yet provided by the same
syntax.  Program text is the only reliable documentation of the system, so
therefore, the language should explicitly support the task of system
documentation.  As with all language, the effectiveness of communication is
dependent upon the skill of the writer.  Good program writers require languages
which support the role of documentation.  They also require that the syntax of
languages is perspicacious, and easy to learn, so that those not necessarily
trained in the skill of 'writing' programs, may be able to read them, and gain
understanding of the system, just as those who enjoy reading novels are not
necessarily novelists. 

Can grafting OO concepts onto a conventional language realise the full benefits
of OO? Perhaps a biblical quote can be applied: "No one sews a patch of unshrunk
cloth on to an old garment; if he does, the patch tears away from it, the new
from the old, and leaves a bigger hole.  No one puts new wine into old
wineskins; if he does, the wine will burst the skins, and then wine and skins
are both lost.  New wine goes into fresh skins." Mark 2:22

There is certainly a rush to the concepts of OO.  OO has been a rallying point
to which good software production practices have been gathered.  These tended to
have been lost in the structured programming world, as it lost its original
focus (which was really object-oriented anyway).  Now that OO has gained
critical mass, it too is in danger of loosing its focus.  OO extensions have
been proposed for COBOL.  One could speculate that there will be an OO BASIC. 

How useful is the extension old technology languages to support some OO
concepts, unless there is a wide scale abandonment of the concepts and practices
in those languages that are contrary to the principles of OOP? How well can such
hybrid languages be expected to support the sophisticated requirements of modern
software production? Surely a basic premise of object-oriented programming is to
enable the development of sophisticated software by the adoption of the simplest
techniques possible, and that the software development techniques and
methodologies do not impede the production of such systems?

All languages are affected by compromise and tradeoffs to some extent.  To what
extent do the decisions made affect the consistency of the concepts within the
language, and the ease with which those concepts can be applied to any given
problem? A language is not just syntactic constructs in isolation, but is the
effects of the combination of all constructs.  It is widely accepted that a good
user interface will not give the user the option of choosing illegal actions, or
combinations of actions.  A poor user interface allows the user to do such
things, perhaps correcting the situation with annoying error messages, or worse
to execute an action that was not intended.  To avoid error is better than to
fix, (most people drive their cars with this principle in mind.) End users of
computers understand such measures of quality, but programmers commonly accept
analogous pit-falls in the languages and tools they use.  Does the extra
complexity disguise such problems, or preclude simple and consistent solutions?

A critique of C++ cannot be separated from criticism of the C base language, as
it is essential for the C++ programmer to be familiar with C, and many of C's
problems affect the way that object-orientation is implemented and used in C++. 
This critique may not be exhaustive of the weaknesses of C++, but it attempts to
illustrate the consequences of these with respect to the timely and economic
production of quality software. 

The format of this report is that it is structured around technical
considerations, with explanations and illustrations of how they affect the
overall goals of software production and object- oriented programming.  Firstly,
it looks at criticisms that may be regarded as specific to C++, then at problems
more general to C, and finally summarises with some conclusions. 

The critique makes two general types of criticism.  Firstly, there are safety
concerns.  These concerns affect the end user perception of the quality of the
program.  They can result in program crashes, or failure to meet requirements. 
Compilers can check for potential crashes, but they can also check for
inconsistencies that may lead to failure to meet requirements.  Languages
provide the framework by which such compiler checking is made possible.  Often
this checking is enabled by requiring the specification of redundant
information.  Declarations themselves are an example of such redundancy.  There
is much redundancy in the source form of a program.  The compiler uses this
information to perform consistency checks, and then throws this information
away, and produces as a result the executable system. 

There is a misconception that safety checks are 'training wheels' for student
programmers.  This is quite an immature conception of programming.  Languages
such as Pascal were widely adopted by universities, because they were good
languages.  The best programmers realise that we are not good programmers, and
as a whole, the computing community is still learning to program. 

Secondly, there are 'courtesy' concerns.  These concerns affect the view of
quality of the program from within the development and maintenance processes.  A
fuller explanation of courtesy concerns is given later.  Courtesy concerns are
generally of a syntactic nature, whereas safety concerns are generally of a
semantic nature. 



                        C++ Specific Criticisms
                        =======================

Virtual Functions
-----------------

Polymorphism and function name overloading are key concepts of OOP.  A language
designers choice is whether this should be specified in the parent or the
inheriting class, in other words, is it the decision of the designer of the
parent class, or the descendant class? There are cases, which can be made for
both.  Indeed both are not mutually exclusive, and can be catered for quite
easily in an object-oriented language.  There are three options:

1) the redefinition of a routine is prohibited, descendant classes must use the
routine as is;

2) a routine may or may not be redefined, descendant classes may use the routine
as provided, or may provide their own implementation, as long as it conforms to
the original interface definition, and accomplishes at least as much;

3) a routine is abstract, and not defined, no implementation is provided, and
non-abstract descendent classes must provide a definition. 

Cases 1 and 3 must naturally be decided by the parent class designer, and the
language should provide appropriate syntax for these cases.  Case 2 is the most
common case and is the decision of the descendant class. 

Option 1

In C++, the first case is not catered for.  The closest is to not specify the
routine as virtual.  However, this means that the routine may be completely
replaced, but is not virtual.  This causes two problems.  Firstly, it means that
the routine may be unintentionally replaced in a descendent.  This error should
be averted by the compiler reporting a syntax error due to 'duplicate
declaration'.  This is logical, as descendant classes actually are part of the
same name space as the classes that they inherit from, and the redeclaration of
a name should cause a name clash within that scope.  (See the section on name
overloading for further explanations.) The second problem is illustrated by the
following example:

class A
{
	public:
	void nonvirt ();
	virtual void virt ();
}

class B : public A
{
	public:
	void nonvirt ();
	void virt ();
}

A a;
B b;
A *ap = &b;

ap->nonvirt ();  // calls A::nonvirt, even though this object is of
                // type B.
ap->virt ();     // calls B::virt, the correct version of the routine
                // for B objects.

In this example, class B has presumably replaced, or added to the functionality
of routines in class A for all objects in the subset class B.  This example
shows how C++'s virtual mechanism breaks a kind of safety.  (This is like type
safety as a routine not intended for objects of type B has been applied to an
object of type B.  But as objects of type B are also of type A, and A::nonvirt
has been written for objects of type A, this cannot be strictly labeled type
safety.  This example shows that there is more to consistency and safety than
can just be termed 'type' safety.) It could be argued that the C programmer
knows what was intended, and this is how the language is defined, so there is no
problem.  If there was no other easy way to achieve the same effect then
undoubtedly there could be no criticism.  However, if the intention is that
sometimes A::nonvirt and sometimes B::nonvirt be applied to objects of type B,
the solution is simple, A::nonvirt and B::nonvirt must have different names. 
This solution means that all erroneous applications of the above construct will
be caught.  That way the compiler can be trusted to detect such potentially
inconsistent situations.  The problem therefore lies with the language
definition.  C++'s non-virtual overloading mechanism precludes this set of
potential errors from being detected. 

A further argument is that any statement should consistently have the same
semantics.  A typical object-oriented interpretation of a statement like a->f ()
is that the most suited implementation of f() is invoked for the object referred
to by 'a'.  In the above example, the programmer must know whether the function
f() is defined virtual or non-virtual in order to interpret exactly what a->f ()
means, and therefore, the statement a->f () is not implementation independent. 
A change in the declaration of f () will change the semantics of the invocation. 
Implementation independence means that a change in the declaration or
implementation DOES NOT change the semantics, of executable statements. 

If a change in the declaration does change the semantics, then this should
generate a compiler detected error, and the programmer must then be required to
make the statement semantically consistent with the changed declaration.  This
reflects the dynamic nature of software development, where the program text is
not static, but is subject to change, and therefore consistency checks are a
necessary part of the role of a compiler. 

For yet another case of inconsistent semantics of the statement a->f () vs
constructors, section 10.9c, p 232 of the C++ ARM should be consulted. 

Option 2

The second case should be left open in the parent class, and the decision left
for the programmer of the descendant class, but C++ generally requires (although
virtual can be introduced in an intermediate class in the inheritance chain)
this decision to be made in the parent class, by the specification of virtual. 
This may be a problem in that it means that routines which aren't actually
redefined in a descendant class are accessed via the slightly less efficient
virtual table technique, rather than a straight procedure call.  (Although this
is never a large overhead, but object-oriented programs tend to use more and
smaller routines, meaning that routine invocation becomes more of a significant
overhead.) An optimising compiler and linker may replace routine invocations
which do not in reality require the virtual mechanism by straight procedure
calls.  The policy should be that any routines which may potentially be
redefined should be declared virtual. 

A potentially more serious problem is that since the programmer of the
descendant class does not specify that the routine is being intentionally
redefined, the original routine may be redefined unwittingly. 
Object-orientation means that the same name can be used, but the programmer
should be conscious that this is what they are doing, and state this explicitly. 
If the programmer is not aware of this then this should be a syntax error
reporting that the name has already been defined.  C++ adopted the original
approach of Simula, and this has been improved upon, and other languages have
adopted better, more explicit approaches, which avoid the error of mistaken
redefinition. 

Eiffel and Object Pascal cater for this situation by requiring the descendant
class programmer to specify in that class, that redefinition is intended.  This
has the extra benefit that a later reader or maintainer of the class will be
able to easily identify which routines have been redefined, and that this
definition is related to a definition in an ancestor class without having to
refer to ancestor class definitions.  Another problem with the C++ scheme is the
ability to reuse function names based on different function signatures.  (See
name overloading for a further and alternative explanation). 

Option 3

The third case is catered for by the pure virtual function, which means the
routine is undefined, and the class is abstract and cannot be directly
instantiated.  If a descendant class is to be instantiated, then it must define
the routine.  Any descendants that do not define the routine, are also abstract
classes.  There is nothing wrong with this in concept, but see the section on
pure virtual functions for a criticism of the syntax. 

Virtual is not an easy notion to grasp.  The related concepts of polymorphism,
redefinition, and overloading are easier to grasp than virtual, being oriented
towards the problem domain.  Virtual routines are the underlying mechanism by
which redefinition in heirs is implemented.  Virtual is an example of where C++
obscures the concepts of OOP as the programmer has to come to terms with the low
level concepts, rather than the higher level object-oriented concepts. 
Interesting as underlying mechanisms may be for the theoretician, or compiler
implementer, the practitioner should not be required to understand or use them
to make sense of the higher level concepts.  Having to use them in practice is
tedious and error-prone, and may prevent the adaptation of software to further
advances in the underlying technology and execution mechanisms (see
concurrency). 

In summary: the concepts which should be expressed are, that a parent class
should be able to prevent redefinition of selected routines, but in general, all
other routines should be left open for redefinition, as prevention of
redefinition is the exception, rather than the norm.  Explicit syntax to provide
the three options makes the programmers intent clear, and can avoid rare, but
albeit subtle and possibly disastrous errors.  To avoid mistaken name
overloading, the redefinition of a routine should be explicitly stated in the
descendant class.  C++'s virtual mechanism may be used in such a way that a kind
of safety similar to type safety, but not strictly type safety, is broken.  A
routine may be applied to an object of a class for which it was not intended.  A
compiler may determine exactly which routines need to be made or not made
virtual. 


The Nature of Inheritance
-------------------------

Inheritance is by nature a close relationship.  Objects that are instances of a
class are by definition also instances of all ancestors of that class.  In order
for there to be effective object- oriented design, the consistency of this
relationship must be preserved.  Consistency can be checked by ensuring that
each redefinition in a subclass is consistent with the corresponding definition
in the ancestor class.  This means that the original design and requirements of
the ancestor class continue to be met through successive levels of refinement. 
If the requirements cannot be met, then this indicates an error in the design or
implementation, and indicates that rework is required.  Thus the consistency due
to inheritance is fundamental to object-oriented design.  C++'s implementation
of non-virtual overloading, and overloading by signature (see below) means that
the compiler cannot check for this consistency.  Therefore C++ does not realise
effective object-oriented design. 


Name overloading
----------------

The choice of names is of fundamental importance in the production of
self-documenting and understandable, and therefore reusable and maintainable
software.  Name overloading is a powerful, but problematic technique.  The
problem with it is that two entities with the same name cause confusion.  This
is easily illustrated by considering a group of people, where two or more have
the same first name.  This problem has been overcome in conventional languages
by providing the notion of scope, where the same name can be used in different
contexts.  For example, brothers in the one family, are usually assigned unique
names by their parents.  When a name is used twice within the same context it
should be a syntax error, due to duplicate definition. 

In object-oriented programming, the scope of a name is the class in which it
occurs.  If a name occurs twice in a class, it is a syntax error.  Inheritance
introduces some questions over and above this simple consideration of scope. 
What should happen to the scope of a declaration in a base class, in a derived
class? There are three choices.  Firstly, the name is in scope only in the
immediate class where it is declared, but not the subclasses.  Secondly, the
name is in scope in a subclass, but the name may be reused, whether the
declaration is virtual or not.  Thirdly, unless the name is reused in the
context of reimplementation of a virtual routine, the reuse of the name is
reported as a syntax error.  This relationship of name scope is obviously not
symmetric, as names in a subclass will not be in scope in a superclass (or
references to objects of the superclass, although this is not the case in
typeless languages such as Smalltalk.)

The first case precludes software reusability, as subclasses will not inherit
definitions of implementation, so case 1 is not worth considering.  The second
case is C++'s approach.  There are two potential problems which may arise. 
Firstly, the name may unintentionally be reused, as the compiler does nothing to
stop this.  Secondly, the parameters being passed to the routine cannot be type
checked against the original routine, as there is not assumed to be any
relationship between the original, and the new routine.  (See the note on name
overloading and nesting for a further explanation of the problems of scoping vs
inheritance).  Since consistency checks between the superclass and subclass are
not possible, the tight relationship that is implied by inheritance, which is
fundamental to the design of object-oriented systems, is not guaranteed.  This
can possibly lead to inconsistencies between the abstract definition of a base
class, and the implementation of a derived class.  If the derived class does not
conform to the base class in this way, it should be questioned why the derived
class is inheriting from the base class in the first place.  (See the nature of
inheritance.)

In order to provide the consistent customisation of reusable software
components, object-oriented programming permits the same name to be used, only
by the redefinition of the original entity.  The programmer of the descendant
class should tell the compiler that this is not a syntax error due to a
duplicate name, but that redefinition is intended.  (This has already been
covered in the virtual section.) In object-oriented programming, two entities
with the same name may be used in the same context.  This conflict must be
resolved by qualifying the names, so that the correct entity is referenced. 
Names must be qualified with the object to which they belong.  Thus two entities
named 'field' may be distinguished by 'a.field', and 'b.field'.  This is a bit
like in a group of people with the same name, qualifying which person is
intended by adding their family name. 

In summary: The relationship of inheritance implies a tight relationship between
base and derived classes.  If consistency checks are not satisfied, then this is
a good indication that the design of the system is incorrect, and detection of
such faulty designs at an early stage will save much wasted work.  C++ fails to
provide an important class of consistency checks. 


Function Overloading
--------------------

C++ allows functions to be overloaded if the arguments in the signature are of
different types.  This has examples where such overloading can be quite useful,
for example:

	max (int, int);
	max (real, real);

will ensure that the best routine for the types int and real will be invoked. 
However, object-oriented programming actually provides a variant on this.  Since
the object is passed to the routine as a hidden parameter ('this' in C++), an
equivalent but more restricted form is already implicitly included in object-
oriented concepts.  A simple example such as the above would be expressed as:

	int i, j;
	real r, s;

	i.max (j);
	r.max (s);

but i.max (r) and r.max (j) will give syntax errors, as the types of the
arguments do not agree.  (By operator overloading of course, these may be better
expressed, i max j and r max s, but min and max are peculiar functions that may
want to accept two or more parameters of the same type.)

Now the above shows that in most cases, function overloading can be expressed
consistently within the object-oriented paradigm, without the need for the
function overloading of C++.  However, C++ makes the notion more general, as the
function may be overloaded by more than one parameter, not just the implicit
current object parameter.  The generalisation of any technique is often a good
thing, as it removes restrictions.  In this case it may provide a useful
mechanism, but how many examples require this flexibility, and how could they be
programmed in other ways?

However, in order to provide such flexibility introduces several potential
errors which the compiler will not be able to check, as it must assume that what
may be inconsistencies, are correct, in order to allow such overloading to be
provided. 

If the programmer intends to redefine a virtual routine, but makes a mistake in
the declaration of the function signature, then an entirely different function
will be assumed to be defined by the compiler, and any calls to the function
using one or other of the signatures will also fail to detect the inconsistency. 

If the programmer does make a mistake in supplying the parameters on a call to
the routine, a C++ compiler will not be able to be specific about the error, all
it can report is that no function with a matching signature could be found. 
Presumably, if the programmer has made this sort of mistake it is for subtle
reasons, and may be difficult to actually pinpoint which parameter was at fault. 
Secondly, the incorrect parameter may accidentally match, one of the other
routines.  In which case this error will be propagated into the production code,
and may not be tracked down for a long time. 

If it is felt though that C++'s scheme of having parameters of different types
is useful though, it should be considered that object-oriented programming does
provide this but in a more restricted and disciplined form.  This is done by
specifying the parameter as needing to conform to a base class, and then any
parameter passed to the routine may be a type of the base class itself, or any
class derived from the base class.  For example:
	
	A.f (B someB) {...};

	class B ...;
	class D : public B ...

	A a;
	D d;

	a.f (d);

This means that strict type checking of parameters is possible, as the entity
'd' must conform to the class 'B'. 

The alternative to function overloading by signature, is to require all names to
be unique.  Names should be the basis of distinction of entities, and is the
purpose of naming.  This is known to work, solves the above problems, as the
compiler can cross check that the parameters supplied are correct for the given
routine name, and furthermore will result in better self-documented software. 
Often it is difficult to choose appropriate names for entities, but this is well
worth the effort.  Philosophically, a name is most important in describing the
characteristics of a person or entity.  Names give things their identity. 

In summary: The relationship of inheritance is a strong relationship.  It is the
basis of reusability, and the ability to be able to assemble software systems
out of preexisting components.  Such assembly requires that consistency between
the components being assembled must be checked.  Assembling a jig-saw requires
that not only the pieces fit in their cut out shape, but that the resultant
picture makes sense.  Assembling software components is more difficult, as
unlike the jig-saw, which is reassembling that which was complete before,
assembling software components is building a system that has never existed
before.  C++ has not implemented the notion of inheritance in a manner that
fully exploits the advantages of inheritance, with regards to software
reusability.  This may lead to the opinion that C++ is not a truly
object-oriented language, as it does not ensure that programs written in the
paradigm are consistent with the paradigm.  Certainly, there are not very many
reusable C++ libraries available, which tends to suggest that C++ may not
support reusability as well as possible.  This means that one of the fundamental
goals of object-oriented programming has not been met. 


A note on name overloading and nesting
--------------------------------------

Name overloading should be provided and used in ways that are explicit and
expected.  Object-oriented programming provides name overloading by inheritance
and redefinition.  Routines provide name overloading, by local declarations of
names being limited in scope to the routine of declaration.  Thus the same name
may be used in other routines, without any clash in the name space.  This
provides an important benefit, the prevention of name clashes, and enables two
programmers to independently write routines without being concerned that the
other programmer is using the same name, so that the modules may later be
combined without the programmers having to worry about name clashes.  Nesting of
routines, or blocks has always had a problem with overloading, in that if a name
is redefined in an inner block, then it may mistakenly override the definition
of the name inherited from an outer block, and the inner block may not access
the entity of the outer block, unless some extraneous entity qualification
mechanism is introduced into the language (which most languages chose not to
provide.)

This report suggests that restriction to only these two mechanisms (redefinition
in subclasses, and local scope to routines) is necessary to gain the maximum
benefit from name overloading.  This is reasoned by common sense, but the proof
of this is in the realm of the language theorist, and beyond the scope of this
document.  The exclusion of all other forms of name overloading results in no
loss of generality, and only the slight inconvenience that the programmer has to
choose unique names, as the compiler will report name clashes as a syntax error
due to 'duplicate identifier'.  Note this is only a potential run time error, if
this level of safety is not provided.  An automated tool such as a compiler has
no way of guessing the programmers intent, and therefore is bound to report this
as an error, as it could be an inconsistency, not because it is an
inconsistency.  The use of name overloading in such forms is frequently found to
be the cause of subtle errors. 

It is also suggested that contrary to the usage in most high level languages,
that a name should not be overloaded, while it is in scope, and that the reuse
of a name in nested procedures and blocks should result in a syntax error.  In
fact textually nested blocks 'inherit' the named entities of outer blocks, and
perhaps as argued for virtual, explicit redefinition of names should perhaps be
provided to avoid error.  However, this actually provides no or very little
benefit, unlike redefinition of inherited entities in the class sense.  Nesting
automatically makes entities tightly coupled. 

Consider another possible error that can happen due to nesting.  If an
overloaded nested declaration is removed, then references to that name will not
result in syntax errors due to their being the same name in the outer
environment.  An instruction mistakenly not removed that changes the value of
the entity, will now mistakenly change the outer entity.  Consider:

{
	int i;
	{
		int i;
		i = 13;
	}
}

Now delete the inner declaration:

{
	int i;

	{
		i = 13;  // Syntactically valid, but not the intention.
	}
}


Such nested blocks as above are also contrary to the principles of
object-oriented programming, as inner blocks are 'tightly' coupled to outer
blocks, and inner blocks have access to the entire environment of the outer
block, instead of through well defined interface routines.  Nested blocks are
the opposite of the loosely coupled software components, which are the ideal of
object-oriented programming.  Nested blocks are similar to parent and child
classes as a child inherits entities defined in a parent, and an inner block
inherits entities defined in an outer block.  This is why textual nesting is not
required in object-oriented languages, as the 'nesting' mechanism of inheritance
is more sophisticated. 

The above example also shows that a program text is not a static entity, but a
dynamic one.  Programmers may easily make modifications to program text that
violates the consistency of the original system.  This scenario happens often,
as a programmer may come back to program segments written some time before, so
that some or many of the original details are forgotten, or modifications may
have to be made by a completely different programmer.  These scenarios are very
common today, especially with large projects, and with software that evolves
over time to meet new and changing customer requirements.  In this case it is
vital that the language and compiler system be trusted to detect any possible
inconsistencies that may be introduced into the system due to modifications. 

The restriction of concepts to their useful forms, is only a restriction on the
language, and coincidentally have the effect of keeping the language simple. 
Such restrictions do not in general restrict the programmer in the production of
flexible, and efficient software, but aid in the production of such software, by
avoiding subtle errors which may otherwise occur.  Such restrictions in the
language and compiler in fact aid flexibility in the software process, as
programmers can more confidently make changes, and rely on the compiler to make
sure that the original programmers intent has not been contravened. 

The most useful tools in any trade are those that do exactly what is needed and
nothing more.  Name overloading is just such a tool.  It is very useful, but
where it is provided in more than its most useful forms, the less useful forms
'blunt' the effectiveness, and even work against the usefulness of overloading,
as confusion of names becomes greater, meaning that name clashes and
inconsistencies of use cannot be detected.  Taken to an extreme, name
overloading may mean that one name can be used for every entity.  Then we may as
well do away with naming entities altogether.  After all, names are not needed
in order to program, the compiler just throws them away in the resultant code. 

In summary: Name overloading is good. Name overloading is bad.


A Note on "A Discipline of Programming"
---------------------------------------

Having written the previous section, 'An essay on the Notion: "The Scope of
Variables"', in E.W.  Djikstra's "A Discipline of Programming", was revisited,
to find very similar reasoning about declarations in nested situations.  The
following points come from his argument. 

Programming techniques facilitate the management of extremely large state
spaces.  There are two notions here, of how to manage the state space, and how
to manage change within the state space.  This is precisely the activity of
programming. 

State spaces may be managed by division, and nomenclature (naming entities),
within those divisions.  The activity of division must cleanly divide the state
space into independent entities, and nomenclature must provide a consistent
system, where name clashes, interference, and confusion are not possible.  A
scheme that implements this will necessarily be as simple as possible. 

State change within a state space, must also be provided in the simplest
possible manner, and one such mechanism is available, and has been used
successfully in many programming languages, the assignment, which changes the
values of variable entities within a state space. 

These notions are the basis of keeping programming a simple and manageable
activity.  The larger state spaces (systems and programs) grow, the more
necessary it is to observe the simplicity of software organisation.  It is the
purpose of programming languages to implement these concepts in the simplest,
and therefore the most manageable forms. 

-- 
Ian Joyner                                     ACSNet: ian@syacus.acus.oz
ACUS (Australian Centre for Unisys Software)   Internet: ian@syacus.acus.oz.au
Tel 61-2-390 1328      Fax 61-2-390 1391       UUCP: ...uunet!munnari!syacus.oz