www.digitalmars.com         C & C++   DMDScript  

digitalmars.D.learn - Templates class member functions not conditional?

reply "monarch_dodra" <monarchdodra gmail.com> writes:
This is related to:
http://d.puremagic.com/issues/show_bug.cgi?id=5193

Basically:
-------
struct S
{
   const int i;
}

struct C(T)
{
     private T val;
      property void front(T value)
         {val = value;} //HERE
}

void main()
{
   C!S test;
}
--------

I wrote a generic template C(T), with a certain member function. 
I created an instance of that template with the parameter S.

Now, I'm getting a compile error at here, which would be 
understandable...
...*if* I was calling said member function.

In C++, member functions of templates are *only* compiled if they 
are ever called. This is not just an optimization, it is meant 
specifically to allow compiling a template, only if a certain 
amount of functionality is required, and the "not required" 
functionality wouldn't compile anyways.

Is this not the case for D? Or is it currently a limitation?
Can I ever expect we'll get a "conditionally compiled on 
requirement" functionality for template struct member functions.


Regarding making the above work, I found 2 solutions:

/////////////////////  1  /////////////////////
struct C(T)
{
     private T val;
     static if(isAssignable(T!T))
     {
          property void front(T value)
             {val = value;}
     }
}

This works but:
a) It looks cludgy, and cumbursome on implementation
b) If I *were* to attemp a call to front, the compile error would 
be an obscure "fucntion not found", as opposed to "can't assign"

/////////////////////  2  /////////////////////
I find this more elegant: Make the member function itself a 
template:

struct C(T)
{
     private T val;
      property void front()(T value)
         {val = value;}
}

This works, and is correctly "conditionally compiled on 
requirement". The signature is kind of kludgy, but it works... 
AND, if someone *does* attempt to make the call, then a verbose 
compile error appears.

Thoughts?
Sep 11 2012
next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
monarch_dodra:

 Is this not the case for D? Or is it currently a limitation?

In this case I think D is working as designed. All functions inside a template are created when you instantiate it.
 Can I ever expect we'll get a "conditionally compiled on 
 requirement" functionality for template struct member functions.

I have asked for a templated(Arg1, Arg2, ...) that's usable for this purpose too, but Walter has not commented so far.
 struct C(T)
 {
     private T val;
     static if(isAssignable(T!T))
     {
          property void front(T value)
             {val = value;}
     }
 }

Probably such use of a static if (or debug, version, etc) is the idiomatic way to do what you want in D. It's easy to see and understand for the person that reads the code.
 struct C(T)
 {
     private T val;
      property void front()(T value)
         {val = value;}
 }

 This works, and is correctly "conditionally compiled on 
 requirement". The signature is kind of kludgy, but it works... 
 AND, if someone *does* attempt to make the call, then a verbose 
 compile error appears.

This doesn't look bad, just remember this doesn't work: struct C(T) { private T val; property void front()(T value) { val = value; } } void main() { C!int ci; auto f = &ci.front; assert(ci.val == 0); f(1); assert(ci.val == 1); } Bye, bearophile
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
 This doesn't look bad, just remember this doesn't work:


 struct C(T) {
     private T val;
      property void front()(T value) {
         val = value;
     }
 }
 void main() {
     C!int ci;
     auto f = &ci.front;
     assert(ci.val == 0);
     f(1);
     assert(ci.val == 1);
 }

 Bye,
 bearophile

True, but this does: auto f = &ci.front!(); That said, it looks horrible, and, client code should not be affected in such a way. So yeah, not a great solution. Plus, it creates a new semantic which is not very obvious. I'll have to admit, I'm really not a fan of of such explicit conditional implementations. It really burdens the code, and forces the implementer to think about the required conditions to use the function, rather than let the compiler issue a failure when it just *does* happen fail. I am opening an enhancement request for this.
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
 monarch_dodra:

 Is this not the case for D? Or is it currently a limitation?

In this case I think D is working as designed. All functions inside a template are created when you instantiate it. [SNIP] Bye, bearophile

Well, the functions are declared inside the template, but do they have to be validated and compiled? I mean, technically, in C++, the functions are also there, just not compiled and validated...
Sep 11 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
monarch_dodra:

 and forces the implementer to think about the required 
 conditions to use the function,

I think this is usually considered a good practice in D, just like using template constraints. If you look in Phobos, similar situations are handled with static ifs. As example see the several static if used inside MapResult() to disable some of its features: https://github.com/D-Programming-Language/phobos/blob/master/std/algorithm.d#L384 private struct MapResult(alias fun, Range) { alias Unqual!Range R; //alias typeof(fun(.ElementType!R.init)) ElementType; R _input; static if (isBidirectionalRange!R) { property auto ref back() { return fun(_input.back); } void popBack() { _input.popBack(); } } this(R input) { _input = input; } static if (isInfinite!R) { // Propagate infinite-ness. enum bool empty = false; } else { property bool empty() { return _input.empty; } } void popFront() { _input.popFront(); } property auto ref front() { return fun(_input.front); } static if (isRandomAccessRange!R) { static if (is(typeof(_input[ulong.max]))) private alias ulong opIndex_t; else private alias uint opIndex_t; auto ref opIndex(opIndex_t index) { return fun(_input[index]); } } static if (hasLength!R || isSomeString!R) { property auto length() { return _input.length; } alias length opDollar; } static if (hasSlicing!R) { static if (is(typeof(_input[ulong.max .. ulong.max]))) private alias ulong opSlice_t; else private alias uint opSlice_t; auto opSlice(opSlice_t lowerBound, opSlice_t upperBound) { return typeof(this)(_input[lowerBound..upperBound]); } } static if (isForwardRange!R) { property auto save() { auto result = this; result._input = result._input.save; return result; } } } Bye, bearophile
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 13:33:08 UTC, bearophile wrote:
 monarch_dodra:

 and forces the implementer to think about the required 
 conditions to use the function,

I think this is usually considered a good practice in D, just like using template constraints. If you look in Phobos, similar situations are handled with static ifs. As example see the several static if used inside MapResult() to disable some of its features: [SNIP] Bye, bearophile

I'd argue it's horrible! Why should the coder bother making these conditional, if calling them would have failed anyways? The fact that it is done like this in D, I'd argue, is by necessity, not necessarily by good practice. IMO, being able to do this is a much better alternative: property auto save() { static assert(isForwardRange!R); //Optional auto result = this; result._input = result._input.save; return result; } It is cleaner and easier on the developer. The exact line that fails is reported (as opposed to "member not found"). An optional static assert can make the reason *why* it failed be bloody explicit. The last reason why this approach is superior, is because above, "front(T)" *does* exist, but it is implemented as "can't work", and calling it MUST fail. This is different from not having it implemented, which allows a third party to define it via UFCS. In that case though, it would not be enriching the interface, it would be hijacking it.
Sep 11 2012
prev sibling next sibling parent "bearophile" <bearophileHUGS lycos.com> writes:
monarch_dodra:

 I'd argue it's horrible! Why should the coder bother making 
 these conditional, if calling them would have failed anyways?

If you see, some of those static ifs also have an else clause. Bye, bearophile
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 15:35:33 UTC, bearophile wrote:
 monarch_dodra:

 I'd argue it's horrible! Why should the coder bother making 
 these conditional, if calling them would have failed anyways?

If you see, some of those static ifs also have an else clause. Bye, bearophile

The only one I really see is for "empty", but I don't really see how that changes anything? That is actually the *only* valid case I can see where usage of a static if is legitimate.
Sep 11 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, September 11, 2012 15:12:57 monarch_dodra wrote:
 On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:
 monarch_dodra:
 Is this not the case for D? Or is it currently a limitation?

In this case I think D is working as designed. All functions inside a template are created when you instantiate it. [SNIP] Bye, bearophile

Well, the functions are declared inside the template, but do they have to be validated and compiled? I mean, technically, in C++, the functions are also there, just not compiled and validated...

Anything inside a template is not compiled unless the template is instantiated, but if the template is instantiated, the entire template is instantiated and compiled (save for static if branches which aren't followed or inner templates which aren't instantiated). If C++ doesn't work that way as well, I find that to be very bizarre. Regardless though, that's how D works. - Jonathan M Davis
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 15:43:56 UTC, Jonathan M Davis 
wrote:
 Anything inside a template is not compiled unless the template 
 is
 instantiated, but if the template is instantiated, the entire 
 template is
 instantiated and compiled (save for static if branches which 
 aren't followed
 or inner templates which aren't instantiated). If C++ doesn't 
 work that way as
 well, I find that to be very bizarre. Regardless though, that's 
 how D works.

 - Jonathan M Davis

TY for putting up with me :D C++ works a certain way, and I find it very bizarre that D doesn't work that way either. Then again, C++ doesn't have static if. At the very least, I wish we could write: -------- struct S(T) { property void front(T value) if(isAssignable!(T,T)) { ... } } -------- That would make things less bulky and the intent much more streamlined with what we already have. In this specific case, this doesn't work because "front" itself isn't a template, but given the encapsulating struct *is* a template, I think it should be made to work. As a matter of fact, I don't see why we can't use conditional implementations ALL the time? Like: If some enum is defined, or if a certain type works or whatever.
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 16:34:33 UTC, monarch_dodra 
wrote:
 At the very least, I wish we could write:
 --------
 struct S(T)
 {
      property void front(T value)
         if(isAssignable!(T,T))
     {
         ...
     }
 }
 --------

..Yeah... that wouldn't work would it? That would just prevent "front" from being matched when the call is attempted, but since it is a non-template, it will still get compiled... Wish it did work though! Well... time to move on...
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 10:51:35 UTC, monarch_dodra
wrote:
 [SNIP]

One of my gripes was that the static if creates a new block, which brakes a struct/class's natural flow. The code gets indented, the DDoc gets placed further away from the function's name, teh condition gets placed kind of far from the function's name etc... However, when written like this: struct C(T) { private T val; // Gets front property T front() {val = value;} //Writes to front static if(isAssignable!(T,T)) property void front(T value) {val = value;} } Then I think it reads alright. I think I'll come to grips with this.
Sep 11 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, September 11, 2012 19:37:02 monarch_dodra wrote:
 However, when written like this:
 
 struct C(T)
 {
       private T val;
 
       // Gets front
        property T front()
       {val = value;}
 
       //Writes to front
       static if(isAssignable!(T,T))
        property void front(T value)
       {val = value;}
 }
 
 Then I think it reads alright.

Whereas I think that that hards readibility, because it hides the fact that a static if is used. If you're submitting code for Phobos, please do something like static if(isAssignable!(T, T)) property void front(T value) {val = value;} or static if(isAssignable!(T,T)) property void front(T value) { value = value; } rather than what you have above, otherwise it will harm maintainability. - Jonathan M Davis
Sep 11 2012
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 17:52:44 UTC, Jonathan M Davis 
wrote:
 On Tuesday, September 11, 2012 19:37:02 monarch_dodra wrote:
 However, when written like this:
 
 struct C(T)
 {
       private T val;
 
       // Gets front
        property T front()
       {val = value;}
 
       //Writes to front
       static if(isAssignable!(T,T))
        property void front(T value)
       {val = value;}
 }
 
 Then I think it reads alright.

Whereas I think that that hards readibility, because it hides the fact that a static if is used. If you're submitting code for Phobos, please do something like static if(isAssignable!(T, T)) property void front(T value) {val = value;} or static if(isAssignable!(T,T)) property void front(T value) { value = value; } rather than what you have above, otherwise it will harm maintainability. - Jonathan M Davis

Hum... Yeah, you are kind of right. I actually am committing something, but the code is a 2 liner. (enforce, then assignment). Is one of these what you are suggesting? //One static if(isAssignable!(T,T)) property void front(T value) { enforce(someCondition) value = value; } or //Two static if(isAssignable!(T,T)) property void front(T value) { enforce(someCondition) value = value; } or //Three static if(isAssignable!(T,T)) property void front(T value) { enforce(someCondition) value = value; } (or just plain) //Four static if(isAssignable!(T,T)) { property void front(T value) { enforce(someCondition) value = value; } } Which do YOU think reads best in this case? That the style I'll use in my submit. I like //Three because it reads like an attribute. Of course, I have no problem submitting it with the default //Four if you think that is best. I'm just trying to do as best possible.
Sep 11 2012
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Tuesday, September 11, 2012 20:39:55 monarch_dodra wrote:
 Is one of these what you are suggesting?
 
 //One
 static if(isAssignable!(T,T))  property void front(T value)
 {
      enforce(someCondition)
      value = value;
 }
 
 or
 
 //Two
 static if(isAssignable!(T,T))
       property void front(T value)
 {
      enforce(someCondition)
      value = value;
 }
 
 or
 
 //Three
 static if(isAssignable!(T,T))
  property void front(T value)
 {
      enforce(someCondition)
      value = value;
 }
 
 
 (or just plain)
 
 //Four
 static if(isAssignable!(T,T))
 {
       property void front(T value)
      {
          enforce(someCondition)
          value = value;
      }
 }
 
 Which do YOU think reads best in this case? That the style I'll
 use in my submit.
 
 I like //Three because it reads like an attribute.
 
 Of course, I have no problem submitting it with the default
 //Four if you think that is best. I'm just trying to do as best
 possible.

I'd use either one or four. I would definitely _not_ use three, precisely because it's using no indentation at all. Either the signature should be one line, or it needs indentation. - Jonathan M Davis
Sep 11 2012
prev sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Tuesday, 11 September 2012 at 21:44:50 UTC, Jonathan M Davis 
wrote:
 I'd use either one or four. I would definitely _not_ use three, 
 precisely
 because it's using no indentation at all. Either the signature 
 should be one
 line, or it needs indentation.

 - Jonathan M Davis

I decided to use the basic notation (Four). I don't think there is anything wrong with experimenting, just not in Phobos. Thankyou.
Sep 11 2012