www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Is this actually supposed to be legal?

reply Jonathan M Davis <jmdavisProg gmx.com> writes:
This code strikes me as being a bug:

--------
class MyBase(T)
{}

class MySubA : MyBase!MySubA
{}

class MySubB : MyBase!MySubB
{}

void main()
{}
--------

but it compiles just fine. However, given the fact that MySubA isn't even 
properly defined until its base class has been defined, I don't see how it
could 
possibly _not_ be a bug for the base class to be templatized on it. You could 
get some really weird behavior if you use compile time reflection on the 
derived class in the base class definition.

Does anyone know if this is actually supposed to work? Or is it in fact a bug 
like I think it is?

- Jonathan M Davis
Jul 16 2012
next sibling parent =?UTF-8?B?QWxleCBSw7hubmUgUGV0ZXJzZW4=?= <alex lycus.org> writes:
On 17-07-2012 07:24, Jonathan M Davis wrote:
 This code strikes me as being a bug:

 --------
 class MyBase(T)
 {}

 class MySubA : MyBase!MySubA
 {}

 class MySubB : MyBase!MySubB
 {}

 void main()
 {}
 --------

 but it compiles just fine. However, given the fact that MySubA isn't even
 properly defined until its base class has been defined, I don't see how it
could
 possibly _not_ be a bug for the base class to be templatized on it. You could
 get some really weird behavior if you use compile time reflection on the
 derived class in the base class definition.

 Does anyone know if this is actually supposed to work? Or is it in fact a bug
 like I think it is?

 - Jonathan M Davis

(Not sure if MySubB was meant to demonstrate anything; it's effectively semantically equal to MySubA.) This code is meant to work. It doesn't actually introduce any circular inheritance. Consider, on the other hand, this: class A : B {} class B : A {} or closer to your example: class A(T) : T {} class B : A!B {} The difference is that here you have direct, circular inheritance, while in your example, the base type is merely parameterized with the deriving type, which is perfectly legal (and trivially resolvable in semantic analysis). -- Alex Rønne Petersen alex lycus.org http://lycus.org
Jul 16 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, July 17, 2012 07:37:38 Alex R=C3=B8nne Petersen wrote:
 (Not sure if MySubB was meant to demonstrate anything; it's effective=

 semantically equal to MySubA.)

It's a simplification of an example in this question: http://stackoverflow.com/questions/11516066/d-inheriting-static-variabl= es- differentiating-by-class which was itself a simplification of other code. - Jonathan M Davis
Jul 16 2012
prev sibling next sibling parent "Chris NS" <ibisbasenji gmail.com> writes:
It is indeed supposed to work, and was actually touted as a 
common and lauded example way back in the day.  However, with the 
advent of this-params for templates it seems less useful now 
(once they've been through the ringer a little more at least).

I did use this-params to great effect in Zeal, to auto-inject 
behavior into subclasses with the proper scoping and other 
concerns.  The base class didn't even have to be a template 
itself (just the magical internals).
https://github.com/csauls/zeal.d/blob/master/source/zeal/base/controller.d

So, to repeat, yes it is supposed to work... but I'm not so sure 
it is such a good idea anymore -- assuming this-params will work 
on the class declaration.

-- Chris NS
Jul 16 2012
prev sibling next sibling parent "Tommi" <tommitissari hotmail.com> writes:
There's a thorough explanation of how "incomplete types" work in 
C++:
http://www.drdobbs.com/the-standard-librarian-containers-of-inc/184403814

And there's some more C++ related stuff:
http://www.boost.org/doc/libs/1_50_0/doc/html/container/containers_of_incomplete_types.html

I wouldn't know about D though.
Jul 17 2012
prev sibling next sibling parent reply "David Nadlinger" <see klickverbot.at> writes:
On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:

 --------
 class MyBase(T)
 {}

 class MySubA : MyBase!MySubA
 {}

 class MySubB : MyBase!MySubB
 {}

 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;) David
Jul 17 2012
next sibling parent Sean Cavanaugh <WorksOnMyMachine gmail.com> writes:
On 7/17/2012 12:23 PM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 14:48:32 David Nadlinger wrote:
 On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:

 --------
 class MyBase(T)
 {}

 class MySubA : MyBase!MySubA
 {}

 class MySubB : MyBase!MySubB
 {}

 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;)

Well, it certainly seems insane to me at first glance - particularly when you take compile time reflection into account, since the derived classes' definitions are now effectively recursive (so I suspect that the situation is worse in D, since C++ doesn't have conditional compliation like D does). But if it's supposed to be legal, I guess that it's suppose to be legal. I'd never seen the idiom before, and it seemed _really_ off to me, which is why I brought it up. But I'd have to study it in order to give an informed opinion on it. - Jonathan M Davis

A 'proper' D port of this kind design would be to use mixins instead of the template. They both accomplish the same thing: The template (or mixins) are written to call functions in the user defined type. A simple example would be the C++ WTL library: A user defined control defines its own window style, but the template code is responsible for creating the window, and accesses the style and class flags from the user defined type. The advantage is the same in both: you avoid making the interface virtual, you still get to use some generic code.
Jul 17 2012
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/17/2012 07:23 PM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 14:48:32 David Nadlinger wrote:
 On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:

 --------
 class MyBase(T)
 {}

 class MySubA : MyBase!MySubA
 {}

 class MySubB : MyBase!MySubB
 {}

 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;)

Well, it certainly seems insane to me at first glance - particularly when you take compile time reflection into account, since the derived classes' definitions are now effectively recursive (so I suspect that the situation is worse in D, since C++ doesn't have conditional compliation like D does).

The fact that it is allowed does not make the compiler's job significantly more complicated. It is not important if the type is passed as a template argument or referred to directly from inside the template -- the issues are the same.
Jul 17 2012
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 07/17/2012 10:50 PM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 22:36:10 Timon Gehr wrote:
 On 07/17/2012 07:23 PM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 14:48:32 David Nadlinger wrote:
 On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:

 --------
 class MyBase(T)
 {}

 class MySubA : MyBase!MySubA
 {}

 class MySubB : MyBase!MySubB
 {}

 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;)

Well, it certainly seems insane to me at first glance - particularly when you take compile time reflection into account, since the derived classes' definitions are now effectively recursive (so I suspect that the situation is worse in D, since C++ doesn't have conditional compliation like D does).

significantly more complicated. It is not important if the type is passed as a template argument or referred to directly from inside the template -- the issues are the same.

The problem is that if you have static ifs and the like in the base class which depends on compile time reflection of the derived class, you effectively have a recursive template definition. e.g. class MyBase(T) { static if(is(typeof(T.func()))) { int func() { return 42; } } } - Jonathan M Davis

This issue is unrelated to CRTP. (also, you probably want to negate that static if condition, otherwise the code is valid and poses no challenge to a compiler.) class MyBase{ static if(!is(typeof(T.func()))) int func() { return 42; } } class T : MyBase { }
Jul 17 2012
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 07/18/2012 11:08 PM, monarch_dodra wrote:
 On Tuesday, 17 July 2012 at 23:38:04 UTC, Jonathan M Davis wrote:
 It's not that it makes the compiler's life hard. It's the fact that
 conditional compilation relies on state that doesn't exist yet. It's
 messed up
 to be checking whether an object defines something when you're in the
 middle of
 defining that object.

 [snip]

 - Jonathan M Davis

Well, while you "can" do it in C++ as the "Curiously Recursive Template Pattern" (particularly popular way of implementing the singleton pattern BTW), you can't just do anything you feel like doing with it. If I remember correctly, in C++, you can't access any of T's members, or create any (stack) instances of T, or (I think) call T's any of T's static members, because "T is not correctly formed yet". Did you try anything more advanced? For example, this outright _crashes_ my (r)dmd: -------- class MyBase(T) { int a = T.hello(); } class MySubA : MyBase!MySubA { static int hello(){return 0;} } --------

Well, that is a bug.
 I'm not entirely sure how valid the comparison with C++'s CRTP is,
 because D's classes are actually pointer to implementation,  but I think
 it is a safe bet that what C++ can't do, neither can D.

Careful there. D allows forward references. This is all supposed to work in D. (but DMD is poor when it comes to tricky symbol lookup tasks.)
Jul 18 2012
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 07/18/2012 01:37 AM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 23:11:43 Timon Gehr wrote:
 This issue is unrelated to CRTP. (also, you probably want to negate
 that static if condition, otherwise the code is valid and poses no
 challenge to a compiler.)

It's not that it makes the compiler's life hard. It's the fact that conditional compilation relies on state that doesn't exist yet. It's messed up to be checking whether an object defines something when you're in the middle of defining that object. ...

Declarations in D are declarative. There is no notion of state.
Jul 18 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, July 17, 2012 14:48:32 David Nadlinger wrote:
 On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:
 
 --------
 class MyBase(T)
 {}
 
 class MySubA : MyBase!MySubA
 {}
 
 class MySubB : MyBase!MySubB
 {}
 
 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;)

Well, it certainly seems insane to me at first glance - particularly when you take compile time reflection into account, since the derived classes' definitions are now effectively recursive (so I suspect that the situation is worse in D, since C++ doesn't have conditional compliation like D does). But if it's supposed to be legal, I guess that it's suppose to be legal. I'd never seen the idiom before, and it seemed _really_ off to me, which is why I brought it up. But I'd have to study it in order to give an informed opinion on it. - Jonathan M Davis
Jul 17 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, July 17, 2012 22:36:10 Timon Gehr wrote:
 On 07/17/2012 07:23 PM, Jonathan M Davis wrote:
 On Tuesday, July 17, 2012 14:48:32 David Nadlinger wrote:
 On Tuesday, 17 July 2012 at 05:24:26 UTC, Jonathan M Davis wrote:
 This code strikes me as being a bug:
 
 --------
 class MyBase(T)
 {}
 
 class MySubA : MyBase!MySubA
 {}
 
 class MySubB : MyBase!MySubB
 {}
 
 void main()
 {}
 --------

This pattern is actually quite common in C++ code, and referred to as CRTP (curiously recurring template pattern). If you propose to kill it, Andrei is going to get mad at you. ;)

Well, it certainly seems insane to me at first glance - particularly when you take compile time reflection into account, since the derived classes' definitions are now effectively recursive (so I suspect that the situation is worse in D, since C++ doesn't have conditional compliation like D does).

significantly more complicated. It is not important if the type is passed as a template argument or referred to directly from inside the template -- the issues are the same.

The problem is that if you have static ifs and the like in the base class which depends on compile time reflection of the derived class, you effectively have a recursive template definition. e.g. class MyBase(T) { static if(is(tyepeof(T.func()))) { int func() { return 42; } } } - Jonathan M Davis
Jul 17 2012
prev sibling next sibling parent "David Nadlinger" <see klickverbot.at> writes:
On Tuesday, 17 July 2012 at 20:50:41 UTC, Jonathan M Davis wrote:
 The problem is that if you have static ifs and the like in the 
 base class
 which depends on compile time reflection of the derived class, 
 you effectively
 have a recursive template definition. e.g.

 class MyBase(T)
 {
  static if(is(tyepeof(T.func())))
  {
  int func() { return 42; }
  }
 }

I don't see how this would be any different than, say, using __traits(derivedMembers, typeof(this)) in a struct/class declaration. That being said, I have never used CRTP in D so far, since template mixins seem to be the better choice in almost all situations. David
Jul 17 2012
prev sibling next sibling parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Tuesday, July 17, 2012 23:11:43 Timon Gehr wrote:
 This issue is unrelated to CRTP. (also, you probably want to negate
 that static if condition, otherwise the code is valid and poses no
 challenge to a compiler.)

It's not that it makes the compiler's life hard. It's the fact that conditional compilation relies on state that doesn't exist yet. It's messed up to be checking whether an object defines something when you're in the middle of defining that object. Now, as David N. points out in another post, this isn't exactly the only case of that. You can make a templated type do it to itself via something like __traits(derivedMembers, typeof(this)), but in this case, you're doing it on a template argument which may or may not be a derived class (though presumably is). So, in any case, it's a problem in that you do have to be careful about doing conditional compilation based on the type, since it's in the middle of being defined, but that's not necessarily enough to merit getting rid of the feature (especially since you can have essentially the same problem even without a base class). - Jonathan M Davis
Jul 17 2012
prev sibling next sibling parent Philippe Sigaud <philippe.sigaud gmail.com> writes:
--f46d0401236fb6d57e04c51bab97
Content-Type: text/plain; charset=UTF-8

 That being said, I have never used CRTP in D so far, since template

FWIW, CRTP is the main reason I used classes in Pegged, to allow grammar rules to refer to themselves. My earlier attempts with structs did not work. So, given a grammar rule like: Expr <- '(' Expr ')' / ... I use: class Expr : Or! (Sequence!(Literal!("("), Expr, Literal!(")")) , ...) { ... } As you can see, class Expr refer to itself while it's not defined yet. It's the main use I've found for this idiom. Many C++ parsers use the same trick and I was particularly glad to see it worked in D too. Most of the times I use mixins, but could not find a way to do the same recursive rule definition with them. IIRC, I talk a bit about the CRTP in my tutorial on D templates , on Github. Philippe --f46d0401236fb6d57e04c51bab97 Content-Type: text/html; charset=UTF-8 <p>&gt; That being said, I have never used CRTP in D so far, since template mixins seem to be the better choice in almost all situations.</p> <p>FWIW, CRTP is the main reason I used classes in Pegged, to allow grammar rules to refer to themselves. My earlier attempts with structs did not work.</p> <p>So, given a grammar rule like:</p> <p>Expr &lt;- &#39;(&#39; Expr &#39;)&#39; / ...</p> <p>I use:</p> <p>class Expr : Or! (Sequence!(Literal!(&quot;(&quot;), Expr, Literal!(&quot;)&quot;)) , ...)<br> { ... }</p> <p>As you can see, class Expr refer to itself while it&#39;s not defined yet. It&#39;s the main use I&#39;ve found for this idiom. Many C++ parsers use the same trick and I was particularly glad to see it worked in D too.</p> <p>Most of the times I use mixins, but could not find a way to do the same recursive rule definition with them.</p> <p>IIRC, I talk a bit about the CRTP in my tutorial on D templates , on Github.<br></p> <p>Philippe<br> </p> --f46d0401236fb6d57e04c51bab97--
Jul 18 2012
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 17 July 2012 at 23:38:04 UTC, Jonathan M Davis wrote:
 It's not that it makes the compiler's life hard. It's the fact 
 that
 conditional compilation relies on state that doesn't exist yet. 
 It's messed up
 to be checking whether an object defines something when you're 
 in the middle of
 defining that object.

 [snip]

 - Jonathan M Davis

Well, while you "can" do it in C++ as the "Curiously Recursive Template Pattern" (particularly popular way of implementing the singleton pattern BTW), you can't just do anything you feel like doing with it. If I remember correctly, in C++, you can't access any of T's members, or create any (stack) instances of T, or (I think) call T's any of T's static members, because "T is not correctly formed yet". Did you try anything more advanced? For example, this outright _crashes_ my (r)dmd: -------- class MyBase(T) { int a = T.hello(); } class MySubA : MyBase!MySubA { static int hello(){return 0;} } -------- I'm not entirely sure how valid the comparison with C++'s CRTP is, because D's classes are actually pointer to implementation, but I think it is a safe bet that what C++ can't do, neither can D.
Jul 18 2012