digitalmars.D.learn - Templates class member functions not conditional?
- monarch_dodra (61/61) Sep 11 2012 This is related to:
- bearophile (24/46) Sep 11 2012 In this case I think D is working as designed. All functions
- monarch_dodra (11/27) Sep 11 2012 True, but this does: auto f = &ci.front!();
- bearophile (89/91) Sep 11 2012 I think this is usually considered a good practice in D, just
- monarch_dodra (23/34) Sep 11 2012 I'd argue it's horrible! Why should the coder bother making these
- bearophile (4/6) Sep 11 2012 If you see, some of those static ifs also have an else clause.
- monarch_dodra (4/10) Sep 11 2012 The only one I really see is for "empty", but I don't really see
- monarch_dodra (5/12) Sep 11 2012 Well, the functions are declared inside the template, but do they
- Jonathan M Davis (7/24) Sep 11 2012 Anything inside a template is not compiled unless the template is
- monarch_dodra (25/36) Sep 11 2012 TY for putting up with me :D
- monarch_dodra (8/19) Sep 11 2012 ..Yeah... that wouldn't work would it?
- monarch_dodra (21/22) Sep 11 2012 One of my gripes was that the static if creates a new block,
- Jonathan M Davis (10/27) Sep 11 2012 Whereas I think that that hards readibility, because it hides the fact t...
- monarch_dodra (44/75) Sep 11 2012 Hum... Yeah, you are kind of right.
- Jonathan M Davis (5/55) Sep 11 2012 I'd use either one or four. I would definitely _not_ use three, precisel...
- monarch_dodra (5/11) Sep 11 2012 I decided to use the basic notation (Four). I don't think there
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
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
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, bearophileTrue, 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
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
On Tuesday, 11 September 2012 at 13:33:08 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? 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.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
Sep 11 2012
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
On Tuesday, 11 September 2012 at 15:35:33 UTC, bearophile wrote:monarch_dodra: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.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
On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile wrote:monarch_dodra: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...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
Sep 11 2012
On Tuesday, September 11, 2012 15:12:57 monarch_dodra wrote:On Tuesday, 11 September 2012 at 11:33:05 UTC, bearophile 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 Davismonarch_dodra: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...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
Sep 11 2012
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 DavisTY 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
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
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
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
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: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.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
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
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 DavisI 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