www.digitalmars.com         C & C++   DMDScript  

D - alias declarations revisited

reply Manfred Nowak <svv1999 hotmail.com> writes:
http://www.digitalmars.com/d/declaration.html, [cited 11.01.04]:
| class C : B {
|   int foo( int a ) { return 3; }
|   alias B.foo foo;
| }

This looks like a bug to me. At least it fells into the categories 
`unused variable, unreachable code, x=x' and therefore a maintenance 
problem.

The declaration `class C : B' denotes, that C inherets every function 
of B by definition. Therefore the alias declaration in this code is at 
least completely superfluous.


The feeling that it should be a bug stems from the fact, that B.foo is 
a set of functions, namely B.foo and A.foo, which is inhereted into B, 
and therefore A.foo is reintroduced into the current scope, despite of 
the fact that it has been just overloaded. But then I read the 
introductory words again:
| Aliases can also 'import' a set of overloaded functions, that can
| be overloaded with functions in the current scope 

This seems to mean, that alias declarations are of lower priority 
and that the textual order of aliasing/overloading does not matter. If 
so, then it imposes a recognition problem: after reading some 
overloading the maintainer eventually reaches an alias declaration that 
'imports' overloadable functions. Those functiomns, that are not 
already overloaded expand the current scope at least temporarily. 
Therefore a maintainer must recall all already declared functions in 
the current scope to determine which of the functions introduced by the 
alias are really expanding the current scope. This holds for every 
newly found alias declaration.

To avoid this the alias declarations must come first, because they are 
of lower priority, than the declarations in the current scope.


But first have a look at the current implementation, when A.foo is 
overloaded in B:
<code>
	class A {
	    int foo(int a) { return 1; }
	}

	class B : A {
	    int foo(int a) { return 4; }  // A.foo overloaded
	    int foo( int a, uint b ) { return 2; }
	}

	class C : B {
	    int foo( int a ) { return 3; }
	    alias B.foo foo;
	}

	class D : C  {
	}


	void test()
	{
	    D b = new D();
	    int i;

	    i = b.foo(1, 2u);	// calls B.foo
         printf("%d\n",i);
	}
	
void main()
{
  test();
}
</code>

The code compiles and the printing consists of the expected number `2'. 
But oops, the call of `i = b.foo(1);' was lost. Reintroducing it:
<code> [...]
	void test()
	{
	    D b = new D();
	    int i;

	    i = b.foo(1, 2u);	// calls B.foo
         i = b.foo(1);       // lost code reintroduced here
         printf("%d\n",i);
	}
[...] </code>

yields the compiler error:
| test.d(25): function foo overloads int(int a) and int(int a) both
| match argumentlist for foo 

This is inacceptable. Usually the class designers are not identical 
with the users of the class. Therefore this behaviour of the compiler 
is equivalent to a qualitiy control by the consumer. And in addition, 
there should be no error message at all, because the questionable 
function was overloaded in the current 'importing' scope.


Now lets change the textual order of the alias and the overloading.
<code> [...]
	class C : B {
	    alias B.foo foo;
	    int foo( int a ) { return 3; }
	}
[...]</code>

This yields the compiler error:
| test.d(6): function foo conflicts with C.foo at test.d(12)

B.foo(int) conflicts with its overloading function C.foo(int)? 
According to the introductuary words this should not happen at all, but 
the error message is presented before the actual call of the consumer 
and that is clearly better.


Conclusion: the actual behaviour of the compiler is inconsistent with 
the supposed meaning of the documentation.


IMO the supposed meaning of the documentation should guide the 
implementation.


Moreover, if the supposed meaning takes the lead, then D would have a 
nice method of contructing new classes. Example:
<code>
class A1 {
  int foo(int a){return 1;};
}

class A2 : A1 {
  int foo(int a, uint b){return 2;}
}

class B1 {
  uint bar(int a){return 3;};
}

class B2 : B1 {
  int bar(int a, uint b){return 4;}
}

class C {
  alias A2.foo xxx;
  alias B2.bar xxx;                 //A2.foo conflicts with B2.bar
  
  int xxx(int a, uint b){return 5;} //conflict resolved by overloading
}                                   //C contains A1.foo, B1.bar, C.xxx

class D : C {
}

void test()
{
  D b = new D();
  int i;

  i = b.xxx(1, 2u);	// calls C.xxx
  printf("%d\n",i);
}
	
void main()
{
  test();
}
</code>

But currently the compiler message is:
| test.d(14): function bar conflicts with C.xxx at test.d(21)

I.e. B2.bar conflicts with C.xxx. And when omitting the overloading the 
underlying conflict is only detected when a consumer tries to call the 
conflictuary function. :-(


So long.

-- 
Fight Spam! Join EuroCAUCE: http://www.euro.cauce.org/  
2EA56D6D4DC41ABA311615946D3248A1
Jan 11 2004
parent reply "Ben Hinkle" <bhinkle4 juno.com> writes:
"Manfred Nowak" <svv1999 hotmail.com> wrote in message
news:btr852$2d1q$1 digitaldaemon.com...
 http://www.digitalmars.com/d/declaration.html, [cited 11.01.04]:
 | class C : B {
 |   int foo( int a ) { return 3; }
 |   alias B.foo foo;
 | }

 This looks like a bug to me. At least it fells into the categories
 `unused variable, unreachable code, x=x' and therefore a maintenance
 problem.

 The declaration `class C : B' denotes, that C inherets every function
 of B by definition. Therefore the alias declaration in this code is at
 least completely superfluous.

D is like C++ in this case. Java allows inherited methods to be involved in overload resolution but C++ doesn't. I think the reason C++ doesn't is that overloading can be surprising when combined with implicit type conversions, causing maintenance problems of its own. Example: class B { void foo(short a) {...} } class C : B { void foo(int a) {...} void bar() { short x=2; foo(x); } } Unless you go poking around in B you'd think that bar would call C.foo but if inherited methods took part in overloading then it would call B.foo. When you explicitly use an alias to bring in the other definition of foo then someone reading C will know to go look in B for more foo definitions. This example seems pretty trivial but imagine if there were 4 or 5 layers of inheritance between B and C. Then again, I'm with you that D should use the Java model - overload with abandon. :-) -Ben
Jan 11 2004
parent reply Manfred Nowak <svv1999 hotmail.com> writes:
Ben Hinkle wrote:

[...]
 causing maintenance problems of its own. Example:

What is wrong with dmd? Trying to test your code completed with a main() function: <code> class B { void foo(short a) {} } class C { void foo(int a) {} void bar() { short x=2; foo(x); } } void main() { C c; c.bar(); } </code> It compiles but produces a runtime-error: Access Violation So long. -- Fight Spam! Join EuroCAUCE: http://www.euro.cauce.org/ 2EA56D6D4DC41ABA311615946D3248A1
Jan 12 2004
parent J Anderson <REMOVEanderson badmama.com.au> writes:
Manfred Nowak wrote:

Ben Hinkle wrote:

[...]
  

causing maintenance problems of its own. Example:
    

What is wrong with dmd? Trying to test your code completed with a main() function: <code> class B { void foo(short a) {} } class C { void foo(int a) {} void bar() { short x=2; foo(x); } } void main() { C c; c.bar(); } </code> It compiles but produces a runtime-error: Access Violation So long.

class B { void foo(short a) {} } class C { void foo(int a) {} void bar() { short x=2; foo(x); } } void main() { C c = new C; c.bar(); }
Jan 12 2004