www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Getting around the non-virtuality of templates

reply Stewart Gordon <smjg_1998 yahoo.com> writes:
I'm coming up against some interesting challenges while porting stuff in my
utility 
library to D2.

Firstly, D2 uses opBinary and opOpAssign, rather than the operator-specific op*
and 
op*Assign.  While the latter still work, they aren't mentioned in the current
D2 docs. 
Which would imply that they're on the way out; however, there's no mention at
https://github.com/D-Programming-Language/d-programming-language.org/blob/master/deprecate.dd

(See also
http://d.puremagic.com/issues/show_bug.cgi?id=7779 )

Still, it seems clear that opBinary/opOpAssign is the D2 way of doing it.  But
it has the 
drawback that, because it's a template, it isn't virtual.  One way about it is
to keep the 
D1-style op functions and make opUnary/opBinary/opOpAssign call these.  But is
there a 
better way?

The other isn't a D2-specific issue, though D2 increases the significance of
it.  I have a 
method with the signature

     Set opAnd(bool delegate(Element) dg)

I would like to enable a user of the library to pass in a delegate whose
parameter is any 
type to which Element is implicitly convertible.  This could be the same type
as Element 
with the top-level constancy changed (probably the main use case), or a type
that is 
distinct beyond the constancy level.  Turning it into a template

     Set opAnd(E)(bool delegate(E) dg)

would address this, but prevent overriding with the appropriate code for each
set 
implementation.

Who else has been faced with this problem?  Have you found a good way of
dealing with it?

Stewart.
Mar 25 2012
next sibling parent James Miller <james aatch.net> writes:
On 26 March 2012 11:36, Stewart Gordon <smjg_1998 yahoo.com> wrote:
 I'm coming up against some interesting challenges while porting stuff in =
my
 utility library to D2.

 Firstly, D2 uses opBinary and opOpAssign, rather than the operator-specif=
ic
 op* and op*Assign. =C2=A0While the latter still work, they aren't mention=
ed in
 the current D2 docs. Which would imply that they're on the way out; howev=
er,
 there's no mention at
 https://github.com/D-Programming-Language/d-programming-language.org/blob=
/master/deprecate.dd
 (See also
 http://d.puremagic.com/issues/show_bug.cgi?id=3D7779 )

 Still, it seems clear that opBinary/opOpAssign is the D2 way of doing it.
 =C2=A0But it has the drawback that, because it's a template, it isn't vir=
tual.
 =C2=A0One way about it is to keep the D1-style op functions and make
 opUnary/opBinary/opOpAssign call these. =C2=A0But is there a better way?

 The other isn't a D2-specific issue, though D2 increases the significance=
of
 it. =C2=A0I have a method with the signature

 =C2=A0 =C2=A0Set opAnd(bool delegate(Element) dg)

 I would like to enable a user of the library to pass in a delegate whose
 parameter is any type to which Element is implicitly convertible. =C2=A0T=
his
 could be the same type as Element with the top-level constancy changed
 (probably the main use case), or a type that is distinct beyond the
 constancy level. =C2=A0Turning it into a template

 =C2=A0 =C2=A0Set opAnd(E)(bool delegate(E) dg)

 would address this, but prevent overriding with the appropriate code for
 each set implementation.

 Who else has been faced with this problem? =C2=A0Have you found a good wa=
y of
 dealing with it?

 Stewart.
Non-virtuality of templates is a general problem at the moment. The issues seem to be around how to handle inheritance of template arguments, and how to dispatch the functions based on a combination of template arguments and class hierarchy. This is a hard problem with no obvious answer. in terms of trying to work around it, perhaps compile-time reflection could help, I haven't encountered this before, but that's where I would start. -- James Miller
Mar 25 2012
prev sibling next sibling parent =?utf-8?Q?Simen_Kj=C3=A6r=C3=A5s?= <simen.kjaras gmail.com> writes:
On Mon, 26 Mar 2012 00:36:08 +0200, Stewart Gordon <smjg_1998 yahoo.com>  
wrote:

 I'm coming up against some interesting challenges while porting stuff in  
 my utility library to D2.

 Firstly, D2 uses opBinary and opOpAssign, rather than the  
 operator-specific op* and op*Assign.  While the latter still work, they  
 aren't mentioned in the current D2 docs. Which would imply that they're  
 on the way out; however, there's no mention at
 https://github.com/D-Programming-Language/d-programming-language.org/blob/master/deprecate.dd

 (See also
 http://d.puremagic.com/issues/show_bug.cgi?id=7779 )

 Still, it seems clear that opBinary/opOpAssign is the D2 way of doing  
 it.  But it has the drawback that, because it's a template, it isn't  
 virtual.  One way about it is to keep the D1-style op functions and make  
 opUnary/opBinary/opOpAssign call these.  But is there a better way?

 The other isn't a D2-specific issue, though D2 increases the  
 significance of it.  I have a method with the signature

      Set opAnd(bool delegate(Element) dg)

 I would like to enable a user of the library to pass in a delegate whose  
 parameter is any type to which Element is implicitly convertible.  This  
 could be the same type as Element with the top-level constancy changed  
 (probably the main use case), or a type that is distinct beyond the  
 constancy level.  Turning it into a template

      Set opAnd(E)(bool delegate(E) dg)

 would address this, but prevent overriding with the appropriate code for  
 each set implementation.

 Who else has been faced with this problem?  Have you found a good way of  
 dealing with it?

 Stewart.
I believe the officially vetted answer is to have the templated calls forward to virtual functions. Of course, if you need to do something specific before handing it there, you're out of luck.
Mar 25 2012
prev sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Sun, 25 Mar 2012 18:36:08 -0400, Stewart Gordon <smjg_1998 yahoo.com>  
wrote:

 I'm coming up against some interesting challenges while porting stuff in  
 my utility library to D2.

 Firstly, D2 uses opBinary and opOpAssign, rather than the  
 operator-specific op* and op*Assign.  While the latter still work, they  
 aren't mentioned in the current D2 docs. Which would imply that they're  
 on the way out; however, there's no mention at
 https://github.com/D-Programming-Language/d-programming-language.org/blob/master/deprecate.dd

 (See also
 http://d.puremagic.com/issues/show_bug.cgi?id=7779 )

 Still, it seems clear that opBinary/opOpAssign is the D2 way of doing  
 it.  But it has the drawback that, because it's a template, it isn't  
 virtual.  One way about it is to keep the D1-style op functions and make  
 opUnary/opBinary/opOpAssign call these.  But is there a better way?
I have definitely had issues with this. In dcollections, I have versions of opBinary commented out, because at the time of writing, templates weren't allowed in the D compiler. I filed this bug: http://d.puremagic.com/issues/show_bug.cgi?id=4174 Looks like it hasn't been closed yet... So for now, I use the undocumented old-style functions. One other thing that this "wrapper" method loses is covariance, which I use a lot in dcollections. I haven't filed a bug on it, but there is at least a workaround on this one -- the template can capture the type of "this" from the call site as a template parameter.
 The other isn't a D2-specific issue, though D2 increases the  
 significance of it.  I have a method with the signature

      Set opAnd(bool delegate(Element) dg)

 I would like to enable a user of the library to pass in a delegate whose  
 parameter is any type to which Element is implicitly convertible.  This  
 could be the same type as Element with the top-level constancy changed  
 (probably the main use case), or a type that is distinct beyond the  
 constancy level.  Turning it into a template

      Set opAnd(E)(bool delegate(E) dg)

 would address this, but prevent overriding with the appropriate code for  
 each set implementation.
What I would do is this (assuming template interfaces worked): Set opAnd(E)(bool delegate(E) dg) if(is(E == Element)) { // call protected virtual opAnd equivalent which takes delegate of Element } Set opAnd(E)(bool delegate(E) dg) if(!is(E == Element) && implicitlyConvertsTo!(E, Element)) { bool _dg(Element e) { return dg(e); } // call protected virtual opAnd equivalent with &_dg } Note, with proper delegate implicit conversions, you could probably get some better optimization (including delegates that only differ by const) by checking if the delegate implicitly converts instead of the element. -Steve
Mar 26 2012
parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 26/03/2012 14:37, Steven Schveighoffer wrote:
<snip>
 So for now, I use the undocumented old-style functions. One other thing that
this
 "wrapper" method loses is covariance, which I use a lot in dcollections. I
haven't filed a
 bug on it, but there is at least a workaround on this one -- the template can
capture the
 type of "this" from the call site as a template parameter.
I can't seem to get this to work at the moment: ---------- class Base { T method(T = typeof(this))() { return this; } } class Derived : Base {} void main() { Base b = new Base; Derived d = new Derived; auto bm = b.method(); auto dm = d.method(); b = d; auto dbm = b.method(); pragma(msg, typeof(bm)); pragma(msg, typeof(dm)); pragma(msg, typeof(dbm)); } ---------- C:\Users\Stewart\Documents\Programming\D\Tests>dmd typeof_this Base Base Base ---------- Or do you mean something else? <snip>
 Set opAnd(E)(bool delegate(E) dg) if(!is(E == Element) &&
implicitlyConvertsTo!(E, Element))
 {
 bool _dg(Element e)
 {
 return dg(e);
 }
 // call protected virtual opAnd equivalent with &_dg
 }
<snip> I was beginning to think along those lines myself. I'll try it and see how I get on. Stewart.
Mar 27 2012
next sibling parent Artur Skawina <art.08.09 gmail.com> writes:
On 03/27/12 17:12, Stewart Gordon wrote:
 On 26/03/2012 14:37, Steven Schveighoffer wrote:
 <snip>
 So for now, I use the undocumented old-style functions. One other thing that
this
 "wrapper" method loses is covariance, which I use a lot in dcollections. I
haven't filed a
 bug on it, but there is at least a workaround on this one -- the template can
capture the
 type of "this" from the call site as a template parameter.
I can't seem to get this to work at the moment: ---------- class Base { T method(T = typeof(this))() { return this; } }
class Base { T method(this T)() { return cast(T)this; } } artur
Mar 27 2012
prev sibling parent reply Stewart Gordon <smjg_1998 yahoo.com> writes:
On 27/03/2012 16:12, Stewart Gordon wrote:
 On 26/03/2012 14:37, Steven Schveighoffer wrote:
 <snip>
 So for now, I use the undocumented old-style functions. One other thing that
this
 "wrapper" method loses is covariance, which I use a lot in dcollections. I
haven't filed a
 bug on it, but there is at least a workaround on this one -- the template can
capture the
 type of "this" from the call site as a template parameter.
I can't seem to get this to work at the moment:
<snip> Just figured how to do it. T method(this T)() { return cast(T) this; } Talk about having not got into D2 programming before.... Stewart.
Mar 27 2012
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 27 Mar 2012 11:26:08 -0400, Stewart Gordon <smjg_1998 yahoo.com>  
wrote:

 On 27/03/2012 16:12, Stewart Gordon wrote:
 On 26/03/2012 14:37, Steven Schveighoffer wrote:
 <snip>
 So for now, I use the undocumented old-style functions. One other  
 thing that this
 "wrapper" method loses is covariance, which I use a lot in  
 dcollections. I haven't filed a
 bug on it, but there is at least a workaround on this one -- the  
 template can capture the
 type of "this" from the call site as a template parameter.
I can't seem to get this to work at the moment:
<snip> Just figured how to do it. T method(this T)() { return cast(T) this; } Talk about having not got into D2 programming before....
One tip -- if you are doing method as above inside a class and not an interface, you can use: cast(T)cast(void*)this; which should avoid the unnecessary dynamic cast. This will *not* work in an interface. I'd argue the compiler should be aware of the special type of T for doing casts... -Steve
Mar 27 2012
parent Stewart Gordon <smjg_1998 yahoo.com> writes:
On 27/03/2012 16:46, Steven Schveighoffer wrote:
<snip>
 One tip -- if you are doing method as above inside a class and not an
interface, you can use:

 cast(T)cast(void*)this;

 which should avoid the unnecessary dynamic cast.
Which would work if the function always returns this. But in the general case of what I'm using it for, it would call a virtual function that is meant to always return an object of the same class as this, but somebody could potentially create a subclass that breaks this rule. But I suppose I could try comparing the .classinfo in an out contract to make sure it doesn't happen....
 This will *not* work in an interface. I'd
 argue the compiler should be aware of the special type of T for doing casts...
What's more, given the "this T" parameter it ought to automatically treat this as being of type T within the body of the function. Essentially, semantically analyse the function when instantiated as a method of T rather than as a method of the class in which it is actually placed. Stewart.
Mar 27 2012