www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Templated struct doesn't need the parameterized type in return type definitions?

reply Andrej Mitrovic <none none.none> writes:
import std.stdio;
import std.traits;
import std.exception;

struct CheckedInt(N) if (isIntegral!N)
{
    private N value;
    
    ref CheckedInt opUnary(string op)() if (op == "++")
    {
        enforce(value != value.max);
        ++value;
        return this;
    }
    
    this(N _value)
    {
        value = _value;
    }
}

I didn't know you could define a return type of a templated struct without
defining the type it is parameterized on. I mean this line:

ref CheckedInt opUnary(string op)() if (op == "++")

I thought for sure I always had to write the parameterized type like so:

ref CheckedInt!(N) opUnary(string op)() if (op == "++")

So I guess this really isn't a question but more of a "oh, I didn't know you
could do that". In fact I rarely see this kind of code in Phobos, most of the
time the parameterized type is specified in these types of cases. Is this
feature described somewhere, because I must have missed it if it is?

As a side-note, auto ref is useful in this case, which is pretty cool:
auto ref opUnary(string op)() if (op == "++")
Mar 08 2011
parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 08 Mar 2011 12:06:08 -0500, Andrej Mitrovic <none none.none> wrote:

 import std.stdio;
 import std.traits;
 import std.exception;

 struct CheckedInt(N) if (isIntegral!N)
 {
     private N value;
    ref CheckedInt opUnary(string op)() if (op == "++")
     {
         enforce(value != value.max);
         ++value;
         return this;
     }
    this(N _value)
     {
         value = _value;
     }
 }

 I didn't know you could define a return type of a templated struct  
 without defining the type it is parameterized on. I mean this line:

 ref CheckedInt opUnary(string op)() if (op == "++")

 I thought for sure I always had to write the parameterized type like so:

 ref CheckedInt!(N) opUnary(string op)() if (op == "++")

 So I guess this really isn't a question but more of a "oh, I didn't know  
 you could do that". In fact I rarely see this kind of code in Phobos,  
 most of the time the parameterized type is specified in these types of  
 cases. Is this feature described somewhere, because I must have missed  
 it if it is?

It is described, but not directly. Look on this page: http://www.digitalmars.com/d/2.0/template.html From there we have these two descriptions: ------------------------ If a template has exactly one member in it, and the name of that member is the same as the template name, that member is assumed to be referred to in a template instantiation: template Foo(T) { T Foo; // declare variable Foo of type T } void test() { Foo!(int) = 6; // instead of Foo!(int).Foo } ------------------------ If a template declares exactly one member, and that member is a class with the same name as the template: template Bar(T) { class Bar { T member; } } then the semantic equivalent, called a ClassTemplateDeclaration can be written as: class Bar(T) { T member; } ------------------------ Also note that structs have the same description. So if you think about it, your code is equivalent to: template CheckedInt(N) if(isIntegral!N) { struct CheckedInt { ... } } If you look at it this way, it makes complete sense that within the struct that's within the template, the struct can refer to itself without the specific instantiation parameters. I think this should really be laid out properly in the docs. I discovered this "trick" while writing dcollections by accident and thought it so awesome that I changed all my code which self-returned (quite a bit). -Steve
Mar 08 2011
next sibling parent reply spir <denis.spir gmail.com> writes:
On 03/08/2011 06:20 PM, Steven Schveighoffer wrote:
 On Tue, 08 Mar 2011 12:06:08 -0500, Andrej Mitrovic <none none.none> wrote:

 import std.stdio;
 import std.traits;
 import std.exception;

 struct CheckedInt(N) if (isIntegral!N)
 {
 private N value;
 ref CheckedInt opUnary(string op)() if (op == "++")
 {
 enforce(value != value.max);
 ++value;
 return this;
 }
 this(N _value)
 {
 value = _value;
 }
 }

 I didn't know you could define a return type of a templated struct without
 defining the type it is parameterized on. I mean this line:

 ref CheckedInt opUnary(string op)() if (op == "++")

 I thought for sure I always had to write the parameterized type like so:

 ref CheckedInt!(N) opUnary(string op)() if (op == "++")

 So I guess this really isn't a question but more of a "oh, I didn't know you
 could do that". In fact I rarely see this kind of code in Phobos, most of the
 time the parameterized type is specified in these types of cases. Is this
 feature described somewhere, because I must have missed it if it is?

It is described, but not directly. Look on this page: http://www.digitalmars.com/d/2.0/template.html From there we have these two descriptions: ------------------------ If a template has exactly one member in it, and the name of that member is the same as the template name, that member is assumed to be referred to in a template instantiation: template Foo(T) { T Foo; // declare variable Foo of type T } void test() { Foo!(int) = 6; // instead of Foo!(int).Foo } ------------------------ If a template declares exactly one member, and that member is a class with the same name as the template: template Bar(T) { class Bar { T member; } } then the semantic equivalent, called a ClassTemplateDeclaration can be written as: class Bar(T) { T member; } ------------------------ Also note that structs have the same description. So if you think about it, your code is equivalent to: template CheckedInt(N) if(isIntegral!N) { struct CheckedInt { ... } } If you look at it this way, it makes complete sense that within the struct that's within the template, the struct can refer to itself without the specific instantiation parameters. I think this should really be laid out properly in the docs. I discovered this "trick" while writing dcollections by accident and thought it so awesome that I changed all my code which self-returned (quite a bit). -Steve

I don't share your enthusiasm, Steven, for this feature (which I did not know). In fact, I tend to consider it a mis-feature. Yet another syntactic special-case for special cases in the language. In this case, there are even 3 ways to write the same thing: CheckedInt CheckedInt!N CheckedInt!(N) And note these variants are low-level ones, morphological rather than syntactic properly speaking. Denis -- _________________ vita es estrany spir.wikidot.com
Mar 08 2011
parent reply David Nadlinger <see klickverbot.at> writes:
On 3/8/11 8:20 PM, spir wrote:
 […] Yet another
 syntactic special-case for special cases in the language. In this case,
 there are even 3 ways to write the same thing: […]

I don't quite get how you think this would be a syntactic special case. As Steve pointed out, »class Foo(T) {}« is merely syntax sugar for »template Foo(T) { class Foo{} }«, and because of this, it would rather be a special case *not* to allow referring to Foo using just that name. When considering this, don't forget that templates in D are little more than parametrized, named scopes. I guess you could argue that »class Foo(T)« as a shorthand is an unnecessary special case, but I very much prefer the benefit in brevity here… David
Mar 08 2011
parent bearophile <bearophileHUGS lycos.com> writes:
David Nadlinger:

 and because of this, it would rather 
 be a special case *not* to allow referring to Foo using just that name.

Right. On the other hand the current straightforward design leads to some bugs too, the give bad error messages: http://d.puremagic.com/issues/show_bug.cgi?id=3950 Bye, bearophile
Mar 08 2011
prev sibling next sibling parent reply "Steven Schveighoffer" <schveiguy yahoo.com> writes:
On Tue, 08 Mar 2011 14:20:40 -0500, spir <denis.spir gmail.com> wrote:

 On 03/08/2011 06:20 PM, Steven Schveighoffer wrote:
 On Tue, 08 Mar 2011 12:06:08 -0500, Andrej Mitrovic <none none.none>  
 wrote:

 import std.stdio;
 import std.traits;
 import std.exception;

 struct CheckedInt(N) if (isIntegral!N)
 {
 private N value;
 ref CheckedInt opUnary(string op)() if (op == "++")
 {
 enforce(value != value.max);
 ++value;
 return this;
 }
 this(N _value)
 {
 value = _value;
 }
 }

 I didn't know you could define a return type of a templated struct  
 without
 defining the type it is parameterized on. I mean this line:

 ref CheckedInt opUnary(string op)() if (op == "++")

 I thought for sure I always had to write the parameterized type like  
 so:

 ref CheckedInt!(N) opUnary(string op)() if (op == "++")

 So I guess this really isn't a question but more of a "oh, I didn't  
 know you
 could do that". In fact I rarely see this kind of code in Phobos, most  
 of the
 time the parameterized type is specified in these types of cases. Is  
 this
 feature described somewhere, because I must have missed it if it is?

It is described, but not directly. Look on this page: http://www.digitalmars.com/d/2.0/template.html From there we have these two descriptions: ------------------------ If a template has exactly one member in it, and the name of that member is the same as the template name, that member is assumed to be referred to in a template instantiation: template Foo(T) { T Foo; // declare variable Foo of type T } void test() { Foo!(int) = 6; // instead of Foo!(int).Foo } ------------------------ If a template declares exactly one member, and that member is a class with the same name as the template: template Bar(T) { class Bar { T member; } } then the semantic equivalent, called a ClassTemplateDeclaration can be written as: class Bar(T) { T member; } ------------------------ Also note that structs have the same description. So if you think about it, your code is equivalent to: template CheckedInt(N) if(isIntegral!N) { struct CheckedInt { ... } } If you look at it this way, it makes complete sense that within the struct that's within the template, the struct can refer to itself without the specific instantiation parameters. I think this should really be laid out properly in the docs. I discovered this "trick" while writing dcollections by accident and thought it so awesome that I changed all my code which self-returned (quite a bit). -Steve

I don't share your enthusiasm, Steven, for this feature (which I did not know). In fact, I tend to consider it a mis-feature. Yet another syntactic special-case for special cases in the language. In this case, there are even 3 ways to write the same thing: CheckedInt CheckedInt!N CheckedInt!(N) And note these variants are low-level ones, morphological rather than syntactic properly speaking.

Here's another thing I found in dcollections which caught me off guard, and which I was glad to be rid of when I switched to not parameterizing the names of self returns: class Collection(T) { Collection!(T) add(T t) { ...; return this; } // 20 other functions like add... } "Hey, wouldn't it be cool if I could add a custom allocator to all classes!?"... class Collection(T, alloc = DefaultAllocator!T) { Collection!(T) add(T t) { ...; return this; } // 20 other now subtly incorrect functions like add... } See the problem? -Steve
Mar 08 2011
parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, March 08, 2011 15:31:37 Andrej Mitrovic wrote:
 On 3/8/11, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 See the problem?

Yup. Btw, does auto ref still suffer from any bugs that I should know about? I've heard it had issues.

I'm not sure that it works correctly with properties at the moment. It _does_ appear in the docs now though. - Jonathan M Davis
Mar 08 2011
prev sibling next sibling parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 3/8/11, Steven Schveighoffer <schveiguy yahoo.com> wrote:
 See the problem?

Yup. Btw, does auto ref still suffer from any bugs that I should know about? I've heard it had issues.
Mar 08 2011
prev sibling parent Nick Treleaven <nospam example.net> writes:
On Tue, 08 Mar 2011 15:25:27 -0500, Steven Schveighoffer wrote:

 "Hey, wouldn't it be cool if I could add a custom allocator to all
 classes!?"...
 
 class Collection(T, alloc = DefaultAllocator!T) {
     Collection!(T) add(T t) { ...; return this; } // 20 other now subtly
     incorrect functions like add...
 }
 
 See the problem?

This seems like a good reason to keep allowing the feature. It would be nice if it could be documented clearly somewhere, maybe here: http://www.digitalmars.com/d/2.0/template.html#ClassTemplateDeclaration
Mar 09 2011