www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - class template specialization and inheritance

reply mki <none none.com> writes:
Hello!

I just discovered the template syntax of D. I am very exited about its
simplicity compared to C++.

Now I ran into a template behavior I do not understand. This code:

*** begin code 1 ***
import std.stdio;

class A { }

class B : A { }

class C(T:A) {
    static void tellMe() {
        writefln("derived from A.");
    }
}

class C(T) {
    static void tellMe() {
        writefln("generic.");
    }
}

void main() {
    C!(A).tellMe();
    C!(B).tellMe();
    C!(int).tellMe();
}
*** end code 1 ***

as expected produces the output:

derived from A.
derived from A.
generic.


But this code
*** begin code 2 ***
import std.stdio;

class A(T) { }

class B(T) : A!(T) { }

class C(TT:A!(T)) {
    static void tellMe() {
        writefln("derived from A!(T).");
    }
}

class C(T) {
    static void tellMe() {
        writefln("generic.");
    }
}

void main() {
    C!(A!(int)).tellMe();
    C!(B!(int)).tellMe();
    C!(int).tellMe();
}
*** end code 2 ***

gives the output:
derived from A!(T).
generic.
generic.


In the second line I would expect the output "derived from A!(T)", like in the
example of code 1. My feeling is that for C!(B!(int))
'class C(TT:A!(T))'
with TT=B!(T) and T=int should be a better specialization than
'class C(T)'
with T=B!(T).

Why is 'class C(T)' chosen here?

Thanks!

PS:
BTW, I thought hard about the question if there is a way to do similar things
in C++, that is to have a specialization of a *class* template which is valid
exactly for all classes of a given type A and all classes *derived* from A. For
function templates one can use a SFINAE-trick (the 'enable_if' template in
boost) to achieve this, but I don't see a way to do this for class templates in
C++. Does anyone have an idea?
May 13 2008
next sibling parent reply Sean Kelly <sean invisibleduck.org> writes:
== Quote from mki (none none.com)'s article
 Hello!
 I just discovered the template syntax of D. I am very exited about its
simplicity compared to C++.
 Now I ran into a template behavior I do not understand. This code:
 *** begin code 1 ***
 import std.stdio;
 class A { }
 class B : A { }
 class C(T:A) {
     static void tellMe() {
         writefln("derived from A.");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A).tellMe();
     C!(B).tellMe();
     C!(int).tellMe();
 }
 *** end code 1 ***
 as expected produces the output:
 derived from A.
 derived from A.
 generic.
 But this code
 *** begin code 2 ***
 import std.stdio;
 class A(T) { }
 class B(T) : A!(T) { }
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A!(int)).tellMe();
     C!(B!(int)).tellMe();
     C!(int).tellMe();
 }
 *** end code 2 ***
 gives the output:
 derived from A!(T).
 generic.
 generic.
 In the second line I would expect the output "derived from A!(T)", like in the
example of code 1. My feeling is that

 'class C(TT:A!(T))'
 with TT=B!(T) and T=int should be a better specialization than
 'class C(T)'
 with T=B!(T).
 Why is 'class C(T)' chosen here?

I believe that template expansion like the above requires an exact match for specialization, so it's basically the same as C++ in this respect. However, I do think that this is confusing in light of the "is" syntax where a colon means something different.
 Thanks!
 PS:
 BTW, I thought hard about the question if there is a way to do similar things
in C++, that is to have a

from A. For function templates one can use a SFINAE-trick (the 'enable_if' template in boost) to achieve this, but I don't see a way to do this for class templates in C++. Does anyone have an idea? Concept checking can be done in D using the following method: template isDerivedFromA( T ) { const isDerivedFromA = is( T : A ); } class C( T, bool derivedFromA : false = isDerivedFromA!(T) ) {} class C( T, bool derivedFromA : true = isDerivedFromA!(T) ) {} Or if a boolean isn't enough, represent the result using a type: template getInfoOn( T ) { static if( foo ) alias Type1 getInfoOn; else static if( bar ) alias Type2 getInfoOn; else alias Type3 getInfoOn; } class C( T, I : Type1 = getInfoOn!(T) ) {} class C( T, I : Type2 = getInfoOn!(T) ) {} class C( T, I : Type3 = getInfoOn!(T) ) {} I believe a similar approach will work in C++, though you have to be a bit more clever about implementing the concept checking code. Sean
May 13 2008
parent reply mki <none none.com> writes:
Sean Kelly Wrote:

 == Quote from mki (none none.com)'s article
 Hello!
 I just discovered the template syntax of D. I am very exited about its
simplicity compared to C++.
 Now I ran into a template behavior I do not understand. This code:
 *** begin code 1 ***
 import std.stdio;
 class A { }
 class B : A { }
 class C(T:A) {
     static void tellMe() {
         writefln("derived from A.");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A).tellMe();
     C!(B).tellMe();
     C!(int).tellMe();
 }
 *** end code 1 ***
 as expected produces the output:
 derived from A.
 derived from A.
 generic.
 But this code
 *** begin code 2 ***
 import std.stdio;
 class A(T) { }
 class B(T) : A!(T) { }
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A!(int)).tellMe();
     C!(B!(int)).tellMe();
     C!(int).tellMe();
 }
 *** end code 2 ***
 gives the output:
 derived from A!(T).
 generic.
 generic.
 In the second line I would expect the output "derived from A!(T)", like in the
example of code 1. My feeling is that

 'class C(TT:A!(T))'
 with TT=B!(T) and T=int should be a better specialization than
 'class C(T)'
 with T=B!(T).
 Why is 'class C(T)' chosen here?

I believe that template expansion like the above requires an exact match for specialization, so it's basically the same as C++ in this respect. However, I do think that this is confusing in light of the "is" syntax where a colon means something different.

In the article http://www.digitalmars.com/d/2.0/templates-revisited.html I found the example class Foo( R, // R can be any type P:P*, // P must be a pointer type T:int, // T must be int type S:T*, // S must be pointer to T C:B, // C must be of class B or derived // from B U:I, // U must be a class that // implements interface I string str = "hello", // string literal, // default is "hello" alias A = B // A is any symbol // (including template symbols), // defaulting to B ) So according to the comment in the 6th line the colon includes inherited classes. Also, in my example "code 1" the colon works this way: Since the second line of the output is "derived from A." C!(B) matches the expression C(T:A) with T=B It seems that the additional template parameter T of class A somehow makes a difference in "code 2". But I still don't see why this should be.
 PS:
 BTW, I thought hard about the question if there is a way to do similar things
in C++, that is to have a

from A. For function templates one can use a SFINAE-trick (the 'enable_if' template in boost) to achieve this, but I don't see a way to do this for class templates in C++. Does anyone have an idea? Concept checking can be done in D using the following method: template isDerivedFromA( T ) { const isDerivedFromA = is( T : A ); } class C( T, bool derivedFromA : false = isDerivedFromA!(T) ) {} class C( T, bool derivedFromA : true = isDerivedFromA!(T) ) {} Or if a boolean isn't enough, represent the result using a type: template getInfoOn( T ) { static if( foo ) alias Type1 getInfoOn; else static if( bar ) alias Type2 getInfoOn; else alias Type3 getInfoOn; } class C( T, I : Type1 = getInfoOn!(T) ) {} class C( T, I : Type2 = getInfoOn!(T) ) {} class C( T, I : Type3 = getInfoOn!(T) ) {} I believe a similar approach will work in C++, though you have to be a bit more clever about implementing the concept checking code.

Up to here this can also be done in C++, for the getInfoOn template there should be some substitute in the boost library. But my problem ist that I need to end up with a class template C(T) depending only on *one* template parameter, the class T (which might depend on a template parameter itself). Currently I have this problem in a programming project in C++, and I did not find a solution. I consider switching to D, but at the moment I don't see a solution in D, either.
May 13 2008
parent Sean Kelly <sean invisibleduck.org> writes:
mki wrote:
 Sean Kelly Wrote:
 
 == Quote from mki (none none.com)'s article
 Hello!
 I just discovered the template syntax of D. I am very exited about its
simplicity compared to C++.
 Now I ran into a template behavior I do not understand. This code:
 *** begin code 1 ***
 import std.stdio;
 class A { }
 class B : A { }
 class C(T:A) {
     static void tellMe() {
         writefln("derived from A.");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A).tellMe();
     C!(B).tellMe();
     C!(int).tellMe();
 }
 *** end code 1 ***
 as expected produces the output:
 derived from A.
 derived from A.
 generic.
 But this code
 *** begin code 2 ***
 import std.stdio;
 class A(T) { }
 class B(T) : A!(T) { }
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 void main() {
     C!(A!(int)).tellMe();
     C!(B!(int)).tellMe();
     C!(int).tellMe();
 }
 *** end code 2 ***
 gives the output:
 derived from A!(T).
 generic.
 generic.
 In the second line I would expect the output "derived from A!(T)", like in the
example of code 1. My feeling is that

 'class C(TT:A!(T))'
 with TT=B!(T) and T=int should be a better specialization than
 'class C(T)'
 with T=B!(T).
 Why is 'class C(T)' chosen here?

it's basically the same as C++ in this respect. However, I do think that this is confusing in light of the "is" syntax where a colon means something different.

In the article http://www.digitalmars.com/d/2.0/templates-revisited.html I found the example class Foo( R, // R can be any type P:P*, // P must be a pointer type T:int, // T must be int type S:T*, // S must be pointer to T C:B, // C must be of class B or derived // from B U:I, // U must be a class that // implements interface I string str = "hello", // string literal, // default is "hello" alias A = B // A is any symbol // (including template symbols), // defaulting to B ) So according to the comment in the 6th line the colon includes inherited classes. Also, in my example "code 1" the colon works this way: Since the second line of the output is "derived from A." C!(B) matches the expression C(T:A) with T=B It seems that the additional template parameter T of class A somehow makes a difference in "code 2". But I still don't see why this should be.

Oops, you're right. Shows how often I specialize templates on classes :-p In any case, I think the current compiler simply treats the "T" in "C(TT : A!(T))" as an unknown specific type rather than a placeholder for "any" type. I tried converting the declaration to "C(TT:A!(int))" and it worked as desired. And unfortunately I don't think D supports template template parameters so you couldn't do something like "C(T!(U):A!(U))" either, even if you knew that the type being passed only had one template parameter. Personally, I think this is a bit of a hole in D's template mechanism. It would be nice if there were a way to break apart template parameter types and determine the component types and such easily. Something like: class C( T1!(x = ...), int n : 1 = x.length, T2 : int = x[0] ) {} Where 'x' becomes a tuple holding all the template parameters of T1. This wouldn't exactly resolve your specific situation however. The easiest thing there would be to simply allow templated types to be supplied without the template parameters for specialization checking: class C( T : A ) {} I haven't thought this through however so there may be problems with it. Sean
May 14 2008
prev sibling next sibling parent reply Fawzi Mohamed <fmohamed mac.com> writes:
On 2008-05-14 00:55:41 +0200, mki <none none.com> said:

 Hello!
 
 I just discovered the template syntax of D. I am very exited about its 
 simplicity compared to C++.
 
 Now I ran into a template behavior I do not understand. This code:
 
 *** begin code 1 ***
 import std.stdio;
 
 class A { }
 
 class B : A { }
 
 class C(T:A) {
     static void tellMe() {
         writefln("derived from A.");
     }
 }
 
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 
 void main() {
     C!(A).tellMe();
     C!(B).tellMe();
     C!(int).tellMe();
 }
 *** end code 1 ***
 
 as expected produces the output:
 
 derived from A.
 derived from A.
 generic.
 
 
 But this code
 *** begin code 2 ***
 import std.stdio;
 
 class A(T) { }
 
 class B(T) : A!(T) { }
 
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }
 
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 
 void main() {
     C!(A!(int)).tellMe();
     C!(B!(int)).tellMe();
     C!(int).tellMe();
 }
 *** end code 2 ***
 
 gives the output:
 derived from A!(T).
 generic.
 generic.
 
 
 In the second line I would expect the output "derived from A!(T)", like 
 in the example of code 1. My feeling is that for C!(B!(int))
 'class C(TT:A!(T))'
 with TT=B!(T) and T=int should be a better specialization than
 'class C(T)'
 with T=B!(T).
 
 Why is 'class C(T)' chosen here?

It looks like a bug to me, I would write a report on http://d.puremagic.com/issues/ maybe you should rewrite the messages to "T is derived from ...", because C is not derived from those classes, and the message is misleading. Fawzi
May 14 2008
parent mki <none none.com> writes:
Since I got no further answer, I decided to do so and filed a bug report:
http://d.puremagic.com/issues/show_bug.cgi?id=2126

~mki

Fawzi Mohamed Wrote:

 On 2008-05-14 00:55:41 +0200, mki <none none.com> said:
 
 Hello!
 
 I just discovered the template syntax of D. I am very exited about its 
 simplicity compared to C++.
 
 Now I ran into a template behavior I do not understand. This code:
 
 *** begin code 1 ***
 import std.stdio;
 
 class A { }
 
 class B : A { }
 
 class C(T:A) {
     static void tellMe() {
         writefln("derived from A.");
     }
 }
 
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 
 void main() {
     C!(A).tellMe();
     C!(B).tellMe();
     C!(int).tellMe();
 }
 *** end code 1 ***
 
 as expected produces the output:
 
 derived from A.
 derived from A.
 generic.
 
 
 But this code
 *** begin code 2 ***
 import std.stdio;
 
 class A(T) { }
 
 class B(T) : A!(T) { }
 
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }
 
 class C(T) {
     static void tellMe() {
         writefln("generic.");
     }
 }
 
 void main() {
     C!(A!(int)).tellMe();
     C!(B!(int)).tellMe();
     C!(int).tellMe();
 }
 *** end code 2 ***
 
 gives the output:
 derived from A!(T).
 generic.
 generic.
 
 
 In the second line I would expect the output "derived from A!(T)", like 
 in the example of code 1. My feeling is that for C!(B!(int))
 'class C(TT:A!(T))'
 with TT=B!(T) and T=int should be a better specialization than
 'class C(T)'
 with T=B!(T).
 
 Why is 'class C(T)' chosen here?

It looks like a bug to me, I would write a report on http://d.puremagic.com/issues/ maybe you should rewrite the messages to "T is derived from ...", because C is not derived from those classes, and the message is misleading. Fawzi

May 24 2008
prev sibling parent reply Edward Diener <eddielee_no_spam_here tropicsoft.com> writes:
mki wrote:
 Hello!
 
 I just discovered the template syntax of D. I am very exited about its
simplicity compared to C++.
 
 Now I ran into a template behavior I do not understand. This code:
 snip...
 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }

Huh ! What is T above ? I do not think that your use of T should be legal. Are you sure you did not mean 'class C(TT:A!(TT)) { etc.' ?
May 14 2008
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Edward Diener wrote:
 mki wrote:
 Hello!

 I just discovered the template syntax of D. I am very exited about its 
 simplicity compared to C++.

 Now I ran into a template behavior I do not understand. This code:

 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }

Huh ! What is T above ? I do not think that your use of T should be legal. Are you sure you did not mean 'class C(TT:A!(TT)) { etc.' ?

I think it's supposed to be legal using: class C(TT:A!(T), T) That is, one template parameter can depend on another in a non-trivial way in theory. But I think the compiler has trouble with such things right now. From what I understand that is the intended way to do C++ things like this in D: template <typename Z> template class C { ... } // specialization for all Z == A<T> for template A and some T template <typename T> template class C< A<T> > { ... } --bb
May 14 2008
parent reply Edward Diener <eddielee_no_spam_here tropicsoft.com> writes:
Bill Baxter wrote:
 Edward Diener wrote:
 mki wrote:
 Hello!

 I just discovered the template syntax of D. I am very exited about 
 its simplicity compared to C++.

 Now I ran into a template behavior I do not understand. This code:

 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }

Huh ! What is T above ? I do not think that your use of T should be legal. Are you sure you did not mean 'class C(TT:A!(TT)) { etc.' ?

I think it's supposed to be legal using: class C(TT:A!(T), T)

This makes sense since T is another template parameter. In the original, quoted further above, there was no second template parameter of T, which should generate a compiler error.
 
 That is, one template parameter can depend on another in a non-trivial 
 way in theory. But I think the compiler has trouble with such things 
 right now.
 
  From what I understand that is the intended way to do C++ things like 
 this in D:
 
 template <typename Z>
 template class C { ... }
 
 // specialization for all Z == A<T> for template A and some T
 template <typename T>
   template class C< A<T> > { ... }

If this is meant as C++ the second use of 'template' each time is incorrect. Otherwise it is correct partial specialization syntax as you mention. But notice that T is a template parameter in your C++ equivalent example while in the OP's original which I cited as erroneous, there is no T as a template parameter.
May 14 2008
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Edward Diener wrote:
 Bill Baxter wrote:
 Edward Diener wrote:
 mki wrote:
 Hello!

 I just discovered the template syntax of D. I am very exited about 
 its simplicity compared to C++.

 Now I ran into a template behavior I do not understand. This code:

 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }

Huh ! What is T above ? I do not think that your use of T should be legal. Are you sure you did not mean 'class C(TT:A!(TT)) { etc.' ?

I think it's supposed to be legal using: class C(TT:A!(T), T)

This makes sense since T is another template parameter. In the original, quoted further above, there was no second template parameter of T, which should generate a compiler error.
 That is, one template parameter can depend on another in a non-trivial 
 way in theory. But I think the compiler has trouble with such things 
 right now.

  From what I understand that is the intended way to do C++ things like 
 this in D:

 template <typename Z>
 template class C { ... }

 // specialization for all Z == A<T> for template A and some T
 template <typename T>
   template class C< A<T> > { ... }

If this is meant as C++ the second use of 'template' each time is incorrect.

Yeh, my C++ is a bit rusty from too much D. :-)
 Otherwise it is correct partial specialization syntax as you 
 mention. But notice that T is a template parameter in your C++ 
 equivalent example while in the OP's original which I cited as 
 erroneous, there is no T as a template parameter.

Right, I was just pointing out what I thought might have been the original poster's intention. --bb
May 14 2008
prev sibling parent reply mki <none none.com> writes:
Thanks for your answers!

Edward Diener Wrote:

 Bill Baxter wrote:
 Edward Diener wrote:
 mki wrote:
 Hello!

 I just discovered the template syntax of D. I am very exited about 
 its simplicity compared to C++.

 Now I ran into a template behavior I do not understand. This code:

 class C(TT:A!(T)) {
     static void tellMe() {
         writefln("derived from A!(T).");
     }
 }

Huh ! What is T above ? I do not think that your use of T should be legal. Are you sure you did not mean 'class C(TT:A!(TT)) { etc.' ?

I think it's supposed to be legal using: class C(TT:A!(T), T)

This makes sense since T is another template parameter. In the original, quoted further above, there was no second template parameter of T, which should generate a compiler error.

I don't see why the original class C(TT:A!(T)) shouldn't be legal. in C++ style syntax I would have //primary template template<typename T> class C {}; //template specialization of C template<typename TT, typename T> class C<TT:A<T> > ... ; (Of course this isn't legal either, because C++ doesn't have the colon-syntax.) Now D does away with primary templates, so the first part can be skipped completely in D. Furthermore, D does not need the line template<typename TT, typename T> because the template parameters of the specialization can be completely deduced from the expression C<TT:A<T> >. All undeclared symbols are template parameters. So to my understanding, C(TT:A(T)) should be legal D and should have the sense I indicated with the C++ style syntax above. Also notice that there is no compiler error message on this expression. For my programming purpose, the important thing is that C(TT:A(T)) is a specialization of C(T), but the suggested C(TT:A(T),T) is _not_. For this reason, I really need the first variant, and not the second one. I would like to hear further opinions on this. I my understanding correct, or not?
May 18 2008
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
mki wrote:
 I don't see why the original
 class C(TT:A!(T))
 shouldn't be legal.
 
 in C++ style syntax I would have
 
 //primary template
 template<typename T>
 class C {};
 
 //template specialization of C
 template<typename TT, typename T>
 class C<TT:A<T> > ... ;
 
 (Of course this isn't legal either, because C++ doesn't have the colon-syntax.)
 
 Now D does away with primary templates, so the first part can be skipped
completely in D.
 Furthermore, D does not need the line
 template<typename TT, typename T>
 because the template parameters of the specialization can be completely
deduced from the expression C<TT:A<T> >. All undeclared symbols are template
parameters.
 
 So to my understanding,
 C(TT:A(T))
 should be legal D and should have the sense I indicated with the C++ style
syntax above. Also notice that there is no compiler error message on this
expression.

From http://www.digitalmars.com/d/1.0/template.html """ Deduction from a specialization can provide values for more than one parameter: template Foo(T: T[U], U) { ... } Foo!(int[long]) // instantiates Foo with T set to int, U set to long """ But (for D1 at least) that doesn't work with argument deduction: void fBaz(T: T[U], U)(T[U] x) { writefln("typeof(T)=",typeid(T)," typeof(U)=",typeid(U)); } ... int[long] z; fBaz(z);
 partialifti.d(24): template partialifti.fBaz(T : T[U],U) 


 For my programming purpose, the important thing is that C(TT:A(T)) is a
specialization of C(T), but the suggested C(TT:A(T),T) is _not_.

According to the documentation D thinks it is. Do you have evidence to the contrary?
 For this reason, I really need the first variant, and not the second one.

 I would like to hear further opinions on this. I my understanding correct, or
not?

Sadly the argument deduction (IFTI) implementation is not very complete. It only works in very specific situations. --bb
May 18 2008