Boost logo

Boost :

From: Brian McNamara (lorgon_at_[hidden])
Date: 2004-02-17 09:56:21


On Tue, Feb 17, 2004 at 12:29:25PM +0100, Gabriel Dos Reis wrote:
> Daniel Frey <daniel.frey_at_[hidden]> writes:
> | Apologies in advance if I jump in too quickly without fully
> | understanding your point, but to me it seems that the current rule is
> | consistent only when viewed from a certain point of view.
>
> Someone has characterized it as inconsistent without giving details as
> to why; all I'm saying is that, if one accepts the principle that
> "scopes nest", then the rule is a logical consequence, hence
> consistent with that principle. Now, it might be that that logical
> consequence is unacceptable for some non-rational reasons, but that
> does not mean the consequence itself is inconsistent.
>
> | The POV is that
> |
> | class B { typename X };
> |
> | template< typename X > class Y // 1
> | : public B // 2
> | { // 3
> | ...
> | };
> |
> | You think that template parameter X is declared in line 1, then hidden
> | by the base classes name introduced by line 2, thus non-accessible in
> | line 3, right? You think of the name scopes to nest in the same order,
> | do you?
>
> What I think is: If we accept the principle that scopes should nest,
> then we should accept its logical consequences; if don't want to
> accept those logical consequences, then we should abandon the idea
> that scopes nest. Which one do you want to pick?

I do find this (Gaby's) argument quite compelling.

Nevertheless, despite its logic, human psychology seems resistant to it.
I have been thinking about it a lot, and I think I finally nailed down
why. Here's my story.

Most scopes nest. Most of the time, introducing a new scope and new
names happens in a way that all of the "new" names are visible at the
point of introduction:

   int x; double y;
   void f( Foo x, Bar z ) { // x and z introduced here, _explicitly_
      // x is a Foo, y a double, z a Bar
   }

As homo sapiens we seem good at coping with this kind of
nesting/shadowing, owing to the explicitness.

However there are two kinds of language constructs which introduce names
in an "invisible" way:

   using namespace foo; // many new names come in from foo
   struct Derived : Base { // many new names come in from Base

These constructs are the potentially problematic ones from a
psychological point of view.

Now, for whatever reason, C++ has a "good" rule with regards to
namespaces bringing in hosts of new names all at one. Witness:
   --------------------------------------------------
   namespace Foo {
      void f() { cout << "1" << endl; }
   }
   
   void f() { cout << "2" << endl; }
   
   using namespace Foo;
   
   int main() {
      f();
   }
   --------------------------------------------------
The compiler warns of an ambiguity. Saying "using namespace foo" does
not shadow outer names, but rather brings them into equal contention in
the current scope. The ambiguity diagnostics are good from the human
perspective; these non-visible-at-the-point-of-introduction names are
the ones we are apt to forget about.

However inheritance is error-prone:
   --------------------------------------------------
   struct Base {
      void f() { cout << "1" << endl; }
   };
   
   ... // maybe many many lines of code here
   
   void f() { cout << "2" << endl; }
   
   struct Derived : Base {
      void g() { f(); }
   };
   
   int main() {
      Derived().g();
   }
   --------------------------------------------------
He who reads the code for Derived is apt to be mistaken about the f()
being called. Unlike namespaces, inheritance introduces "invisible" new
names in a way in which they hide/shadow the old names, rather than
merely coming into equal contention (and causing an ambiguity error
requiring the programmer to explicitly specify what is desired).

The "moral" I am trying to sell is that this issue has nothing to do
with templates. It is "inheritance" which is the error-prone construct
here. If the rules for inheritance worked like the rules for "using
namespace", then I think this "intuitive naming issue" would be solved.

That said, I have not given any thought/consideration to what the
implications would be for changing the way "inherited names" work in the
language. Offhand it seems like currently legal programs would either
stay legal with the same semantics, or become non-well-formed (compiler
diagnostic). But I don't want to put the cart ahead of the horse yet.
Does anyone agree with my "moral"? Let's stick with that question
first.

-- 
-Brian McNamara (lorgon_at_[hidden])

Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk