Steven Schveighoffer wrote:
On Tue, 22 Sep 2009 21:25:44 -0400, Jeremie Pelletier
<[email protected]> wrote:
Steven Schveighoffer wrote:
On Tue, 22 Sep 2009 20:49:59 -0400, Jeremie Pelletier
<[email protected]> wrote:
Andrei Alexandrescu wrote:
Hello,
Today, overriding functions have covariant return types:
class A {
A clone();
}
class B : A {
B clone(); // fine, overrides A.clone
}
That is entirely principled and cool. Now the entire story is that
overriding function may have not only covariant return types, but
also contravariant argument types:
class A {
A fun(B);
}
class B : A {
B fun(A); // fine (in theory), overrides A.fun
}
Today D does not support contravariant arguments, but Walter told
me once he'd be quite willing to implement them. It is definitely
the right thing to do, but Walter would want to see a compelling
example before getting to work.
Is there interest in contravariant argument types? If so, do you
know of a killer example?
Thanks,
Andrei
I can't think of an use for contravariant parameters, since a B is
guaranteed to always be a A, I don't see the point of being able to
declare fun(A).
However, I would love to hear about covariant parameters, it would
be most useful for interface implementations:
interface A {
A fun(A);
}
class B : A {
B fun(B);
}
class C : A {
C fun(C);
}
Currently you need some pretty boring boilerplate code, which isn't
complicated but gets repetitive when you have hundreds of such cases:
class B : A {
B fun(A) {
if(B b = cast(B)b) // do stuff
else throw Error("Invalid object type");
}
}
I don't know if this is possible:
A a = new C;
a.fun(new A); // oops, you just passed an A into a function which
requires a C!
Are you suggesting that the compiler insert dynamic cast checks
everywhere? Cause that seems like a lot of overhead...
-Steve
Not everywhere, only where it detects covariant/contravariant
overrides or implementations. In these cases you would already use
explicit dynamic casts so the compiler generated code would just lower
the required boilerplate.
I don't think it's worth the trouble. Dynamic casts are not as cheap as
implicit casts. Contravariance on parameters can be statically proven
by the compiler. I agree Andrei's example isn't that compelling (to be
fair, he did ask if anyone had a good example, indicating his wasn't),
but there are other examples that are more compelling (see the bug
report I referenced in a separate sub-thread).
For instance, if you only ever use class C, and never instantiate an A
or B instance, you still pay the dynamic cast penalty every time you
call fun! It doesn't sound to me like a good design.
I suppose you probably have run into this before, perhaps a real example
would be more convincing.
-Steve
Yeah most of my display interfaces would make use of covariant
arguments, I use main abstract factory for the entire package, and the
objects it creates contain factory methods themselves. I plan to have
implementations for all of win32, gdk, x11, quartz, cairo, pango, d2d,
dwrite, gl, gl3 and finally d3d7 up to d3d11. Most of the client code
will therefore see only the interfaces in order to maintain portability,
and to allow different implementations to live in the same executable
(for example win32/gl/cairo/pango for up to vista or
win32/d3d/d2d/dwrite if on win7 and up).
Here is a watered down version of a few interfaces I use, which are used
by client code:
interface IDrawable {}
interface IWindow : IDrawable {} // onscreen drawable
interface ISurface : IDrawable {} // offscreen drawable
interface IDisplayContext {} // base of 2d-3d contextes
interface IRenderContext {} // 3d context
interface IWindowRenderContext {} // specialized onscreen 3d context
interface IRenderer {
IWindowRenderContext CreateRenderContext(IWindow);
ISurfaceRenderContext CreateRenderContext(ISurface);
}
And some of their current implementation, which are all used within the
package:
abstract class Win32Drawable : IDrawable {}
final class Win32Window : Win32Drawable, IWindow {}
final class Win32Surface : Win32Drawable, IWindow {}
final class GLRenderer : IRenderer {
GLWindowRenderContext CreateRenderContext(IWindow window) {
if(auto win32Window = cast(Win32Window)window)
return new GLWindowRenderContext(win32Window);
else throw new Error();
}
GLSurfaceRenderContext CreateRenderContext(ISurface surface) {
if(auto win32Surface = cast(Win32Surface)surface)
return new GLSurfaceRenderContext(win32Surface);
else throw new Error();
}
}
abstract class GLRenderContext : IRenderContext {}
final class GLWindowRenderContext : GLRenderContext, IWindowRenderContext {
this(Win32Window) {}
}
final class GLSurfaceRenderContext : GLRenderContext,
ISurfaceRenderContext {
this(Win32Surface) {}
}
I have over a hundred of such methods doing dynamic casts across all the
different implementations like these twos in this package alone, a
display interface is quite a large beast.
Of course if you can suggest a better way of doing methods expecting a
specific implementation of an object, while still allowing client code
to call them with the interface pointer, I'd be glad to implement it :)
Jeremie