digitalmars.D.learn - Generic Property Implementation
- bauss (106/106) Feb 20 2018 Would there be a reason why this wouldn't be a good
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (50/60) Feb 20 2018 What is the intended use case for this? The main feature seems to
- Mike Franklin (17/22) Mar 08 2018 I am very interested in this as a potential alternative to the
- Mike Franklin (3/10) Mar 08 2018 * And should be scalable to data that isn't addressable (e.g.
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (60/68) Mar 09 2018 This is the best I've come up with in the current language:
- Mike Franklin (36/73) Mar 09 2018 This looks like a related issue:
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (66/109) Mar 10 2018 This has nothing to do with alias this, opCmp, or opEquals. It's
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (22/39) Mar 12 2018 Actually, this is not fixable. If we consider for a moment the
- Alex (35/87) Mar 12 2018 This is strange...
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (52/91) Mar 12 2018 But I don't have a hook to update the pointer when the struct is
- Alex (22/115) Mar 12 2018 Sure, you have.
- Simen =?UTF-8?B?S2rDpnLDpXM=?= (50/89) Mar 12 2018 True. Can you fix these, too?
- Alex (47/140) Mar 12 2018 ok... this is a little bit more tricky, as there is no assignment
Would there be a reason why this wouldn't be a good implementation? If so what and how could it be improved? Are there flaws in an implementation like this? struct Property(T, bool readOnly = false) { import std.traits : isScalarType, isArray, isAssociativeArray, isSomeString; private T _value; T __GET() { return _value; } static if (readOnly) { private { void __SET(T newValue) { _value = newValue; } void opAssign(T newValue) { __SET(newValue); } } } else { void __SET(T newValue) { _value = newValue; } void opAssign(T newValue) { __SET(newValue); } } bool opEquals(T other) { static if (isScalarType!T || isArray!T || isAssociativeArray!T) { return _value == other; } else { if (_value is other) return true; if (_value is null || other is null) return false; if (typeid(_value) == typeid(other)) return _value.opEquals(other); return _value.opEquals(other) && other.opEquals(_value); } } static if (!(isArray!T) && !(isAssociativeArray!T)) { int opCmp(T other) { static if (isScalarType!T) { if (_value < other) return -1; if (_value > other) return 1; return 0; } else { return _value.opCmp(other); } } } string toString() { static if (isArray!T && isSomeString!T) { return _value; } else static if (__traits(hasMember, T, "toString")) { return _value.toString(); } else { import std.conv : to; return to!string(_value); } } alias __GET this; } alias ReadOnlyProperty(T) = Property!(T, true); --- This would allow something like this: class Foo { ReadOnlyProperty!int bar; Property!string baz; Property!int faz; Property!Bar bar2; } class Bar { int boo; } Which can be used like: auto foo = new Foo; foo.baz = "Hello"; foo.baz = foo.baz ~ " World!"; foo.faz = 250 + foo.bar; foo.bar2 = new Bar; foo.bar2.boo = 200; import std.stdio; writeln(foo.bar); writeln(foo.baz); writeln(foo.faz); writeln(foo.bar2.boo);
Feb 20 2018
On Tuesday, 20 February 2018 at 14:34:53 UTC, bauss wrote:Would there be a reason why this wouldn't be a good implementation?What is the intended use case for this? The main feature seems to be an ability to have read-only members, which is nice. Are there other benefits?If so what and how could it be improved? Are there flaws in an implementation like this?[snip]bool opEquals(T other)Why not use the default == implementation? It's supposed to do the right thing. Yours will fail for structs and classes. Since you're using alias this, it shouldn't even be necessary to implement opEquals - the right one should be called automagically.static if (!(isArray!T) && !(isAssociativeArray!T)) { int opCmp(T other)This will give unexpected errors when comparing structs or classes that don't overload opCmp. Better to replace the static if with is(typeof(T.init < T.init)), or better yet, std.traits.isOrderingComparable. Also, it's not necessary - alias this ensures that the correct opCmp function is called.string toString()Why not use std.typecons.to for everything in here? Its implementors probably have thought of many more corner cases than you have. There is an argument to be made for compile-time overhead, I guess. So, my version of the same: struct Property(T, bool readOnly = false) { private T _value; static if (readOnly) T __GET() { return _value; } else ref T __GET() { return _value; } alias __GET this; string toString() { import std.conv : to; return _value.to!string; } } One more thing: while you provided some very nice examples of how to use the type, it would be nice to have these more test-like. Something like this: unittest { auto foo = new Foo; foo.baz = "Hello"; // Check that value was set correctly: assert(foo.baz == "Hello"); // You can even check if something will compile: assert(!__traits(compiles, foo.bar = 24)); } Sorry if this came off a bit crass - I don't understand when I should use it, so it's hard to give feedback on that. -- Simen
Feb 20 2018
On Tuesday, 20 February 2018 at 14:34:53 UTC, bauss wrote:Would there be a reason why this wouldn't be a good implementation? If so what and how could it be improved? Are there flaws in an implementation like this? [... snip ...]I am very interested in this as a potential alternative to the binary assignment operators for properties DIP (https://github.com/JinShil/DIPs/blob/master/DIPs/DIP1xxx-mvf.md) I would like to know if this can be improved to support the following: * binary assignment operators (e.g. +=) * unary assignment operators (e.g. ++) * safe, nogc, and -betterC compatible * at least as good code generation as that proposed in the DIP when optimizations are enabled. D has the philosophy that the language should strive to provide composable primitives, and delegate syntactic sugar to libraries. I like that philosophy and I think it would prevent an expense of limited resources if we could find a good library implementation for this. Mike
Mar 08 2018
On Friday, 9 March 2018 at 01:22:15 UTC, Mike Franklin wrote:I would like to know if this can be improved to support the following: * binary assignment operators (e.g. +=) * unary assignment operators (e.g. ++) * safe, nogc, and -betterC compatible * at least as good code generation as that proposed in the DIP when optimizations are enabled.* And should be scalable to data that isn't addressable (e.g. bitfields). See https://issues.dlang.org/show_bug.cgi?id=16187
Mar 08 2018
On Friday, 9 March 2018 at 01:22:15 UTC, Mike Franklin wrote:* binary assignment operators (e.g. +=) * unary assignment operators (e.g. ++) * safe, nogc, and -betterC compatible * at least as good code generation as that proposed in the DIP when optimizations are enabled. * And should be scalable to data that isn't addressable (e.g. bitfields). See https://issues.dlang.org/show_bug.cgi?id=16187This is the best I've come up with in the current language: struct S { int n; mixin property!("field", "get => this.n", "set => this.n = set"); } unittest { import std.conv : to; S s; s.field = 3; assert(s.field == 3); s.field += 2; assert(s.field == 5); s.field++; assert(s.field == 6); assert(s.field.to!string == "6"); } Sadly, there are issues: 1) Wrong return type: unittest { S s; auto a = s.field; // Fails - typeof(a) is Property!((get) => this.n, (set) => this.n = set) assert(is(typeof(a) == int)); } Not a whole lot to do about this. I've pondered writing a DIP on auto-decaying/rvalue types, which would decay to a different type the moment they're passed to a function or assigned to a variable. The feature would probably not be worth the trouble, though. 2) Noisy syntax: If I had my druthers, mixin templates would be mixin'd automatically, and eponymous mixin templates would be a thing. That would give us this syntax: struct S { int n; property!("get => this.n", "set => this.n = set") field; } 3) Stringy functions: The string literal functions are an eyesore, but would require some compiler work to fix. This fails: struct S { int n; mixin property!("field", get => this.n, set => this.n = set); } The reason is there's no 'this' at the point of instantiation, since we're in the context of the type, not a function where 'this' makes sense. It seems to me this should be fixable, but I have no idea how much work would be required. 4) 'this' in function bodies It should be possible to write "get => this.n" as "get => n". There's no ambiguity as to which 'n' I'm referring to. Filed a bug: https://issues.dlang.org/show_bug.cgi?id=18584 Implementation: https://gist.github.com/Biotronic/5849af011cbe9c7ea05515011d5995bf -- Simen
Mar 09 2018
On Friday, 9 March 2018 at 14:46:04 UTC, Simen Kjærås wrote:This is the best I've come up with in the current language: struct S { int n; mixin property!("field", "get => this.n", "set => this.n = set"); }Not bad. Not good, but not bad either.Sadly, there are issues: 1) Wrong return type: unittest { S s; auto a = s.field; // Fails - typeof(a) is Property!((get) => this.n, (set) => this.n = set) assert(is(typeof(a) == int)); }This looks like a related issue: https://issues.dlang.org/show_bug.cgi?id=16657. That's is a deal-breaker for me, but I think it could be fixed.2) Noisy syntax: If I had my druthers, mixin templates would be mixin'd automatically, and eponymous mixin templates would be a thing.Yes, this would be nice, but I don't think it's a deal-breaker.3) Stringy functions: The string literal functions are an eyesore, but would require some compiler work to fix. This fails: struct S { int n; mixin property!("field", get => this.n, set => this.n = set); } The reason is there's no 'this' at the point of instantiation, since we're in the context of the type, not a function where 'this' makes sense. It seems to me this should be fixable, but I have no idea how much work would be required.Yeah, that's quite unfortunate; writing code in strings stinks. I actually prefer the DIP for this issue alone.4) 'this' in function bodies It should be possible to write "get => this.n" as "get => n". There's no ambiguity as to which 'n' I'm referring to. Filed a bug: https://issues.dlang.org/show_bug.cgi?id=18584Thanks for filing the issue. I just might be able to fix it; I'll try.Implementation: https://gist.github.com/Biotronic/5849af011cbe9c7ea05515011d5995bfYou've done some great work here. I spent about an hour on this yesterday, and my implementation started to require more and more mixing strings to get it to work. If all of the issues you've identified were addressed, you'd end struct S { int n; property! ( get => { n }, set => { n = set } ) field; } That's actually pretty darn good. It makes me wonder if I should work on fixing those issues or continue with the DIP. If you don't have any objections I'd like to incorporate this implementation and your analysis into the DIP. Thank you again for doing this; you've saved me a awful lot of time, and have done more than I probably could have. Mike
Mar 09 2018
On Friday, 9 March 2018 at 23:34:56 UTC, Mike Franklin wrote:On Friday, 9 March 2018 at 14:46:04 UTC, Simen Kjærås wrote:This has nothing to do with alias this, opCmp, or opEquals. It's simply that instead of returning an int, like the original property did, s.field returns a wrapper type. There's no real way around this. Actually, there may be a more interesting way to implement the DIP I mentioned - what if named mixin templates could have overloaded operators and alias this? What if this worked: struct S { int n; mixin property!(get => n, set => n = set) field; } template property(fns...) { alias getter = findGetter!fns; alias setter = findSetter!fns; auto get() { return getter(DummyType.init); } alias get this; alias setter opAssign; auto opOpAssign(string op, T)(T value) { /* ... */ } // other overloads } unittest { S s; auto a = s.field; // Not a type, so alias this is used. assert(is(typeof(a) == int)); // overloaded operators s.field += 4; ++s.field; } From what I can see, the only necessary change would be that alias this and operator overloads be looked for in named mixin templates. Sounds doable. This would both enable auto-decaying/rvalue types, and allow for a bunch of interesting stuff with operator overloads. ...but there's an issue with this compared to real rvalue types - you can't return them from a function. One use case for rvalue types is short-circuiting when (a op1 b op2 c) can be done faster than ((a op1 b) op2 c), for instance when a, b, and c are BigInts, and op1 = ^^, op2 = %. Named mixin templates with overloaded operators cannot do this.1) Wrong return type: unittest { S s; auto a = s.field; // Fails - typeof(a) is Property!((get) => this.n, (set) => this.n = set) assert(is(typeof(a) == int)); }This looks like a related issue: https://issues.dlang.org/show_bug.cgi?id=16657. That's is a deal-breaker for me, but I think it could be fixed.Agreed. I filed an issue on the eponymous mixin templates: https://issues.dlang.org/show_bug.cgi?id=185862) Noisy syntax: If I had my druthers, mixin templates would be mixin'd automatically, and eponymous mixin templates would be a thing.Yes, this would be nice, but I don't think it's a deal-breaker.I'm not sure how fixable this is, but I am sure that there's plenty of benefit to being able to write code like this: struct S { int n, m; SomeType!(() => n + m) a; } over this: struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } } Anyways, I filed a bug for it: https://issues.dlang.org/show_bug.cgi?id=185873) Stringy functions: The string literal functions are an eyesore, but would require some compiler work to fix.Yeah, that's quite unfortunate; writing code in strings stinks. I actually prefer the DIP for this issue alone.If all of the issues you've identified were addressed, you'd struct S { int n; property! ( get => { n }, set => { n = set } ) field; } That's actually pretty darn good. It makes me wonder if I should work on fixing those issues or continue with the DIP.The real benefit to doing it outside the DIP is that the features can be used in other contexts. Rvalue types definitely have some more uses, the 'this.n' issue is simply a bug, eponymous mixin templates seem useful to me, and being able to use delegates as template parameters outside of member functions would make many things clearer.If you don't have any objections I'd like to incorporate this implementation and your analysis into the DIP.Of course.Thank you again for doing this; you've saved me a awful lot of time, and have done more than I probably could have.Pleasure. I've been spending some time on this myself, so it's not the first time I've tried to make a palatable implementation. -- Simen
Mar 10 2018
On Saturday, 10 March 2018 at 17:43:06 UTC, Simen Kjærås wrote:I'm not sure how fixable this is, but I am sure that there's plenty of benefit to being able to write code like this: struct S { int n, m; SomeType!(() => n + m) a; } over this: struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } } Anyways, I filed a bug for it: https://issues.dlang.org/show_bug.cgi?id=18587Actually, this is not fixable. If we consider for a moment the internals of SomeType in the above code, it's something like this: struct SomeType(alias fn) { S* context; } The full layout of S then becomes this: struct S { int n; int m; S* a.context; // points to 'this'. } Since structs in D are defined to be movable by blitting only, with no cleanup afterwards, internal pointers are a no-no. Ref https://dlang.org/spec/garbage.html:Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results.One possible solution would be for the above code to generate the function a() instead of making 'a' a member. This has its own set of issues, though - suddenly something that looks like a member isn't one, and its address can't be taken, it can't be passed by ref, etc. -- Simen
Mar 12 2018
On Monday, 12 March 2018 at 07:04:19 UTC, Simen Kjærås wrote:On Saturday, 10 March 2018 at 17:43:06 UTC, Simen Kjærås wrote:This is strange... An incomplete type is perfectly ok, so there should be no problem with a pointer of the same type inside a struct. If accidentally the pointer refers "this", then it must have been set after construction. As before construction the value of "this"-pointer does not exist and after the construction the pointer to "this" has the default value of null. Therefore, you are aware of this circumstance and appropriate movings of the pointer value are also up to you. On the other side: Isn't it the case, that struct SomeType(alias fn) {} is (or has to be) lowered to something like struct SomeType { typeof(fn)* fn; } Even if fn contains a frame pointer to S it is perfectly legal to have such a type. SomeType would contain a delegate then. However, I think, the syntaxI'm not sure how fixable this is, but I am sure that there's plenty of benefit to being able to write code like this: struct S { int n, m; SomeType!(() => n + m) a; } over this: struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } } Anyways, I filed a bug for it: https://issues.dlang.org/show_bug.cgi?id=18587Actually, this is not fixable. If we consider for a moment the internals of SomeType in the above code, it's something like this: struct SomeType(alias fn) { S* context; } The full layout of S then becomes this: struct S { int n; int m; S* a.context; // points to 'this'. } Since structs in D are defined to be movable by blitting only, with no cleanup afterwards, internal pointers are a no-no. Ref https://dlang.org/spec/garbage.html:Do not have pointers in a struct instance that point back to the same instance. The trouble with this is if the instance gets moved in memory, the pointer will point back to where it came from, with likely disastrous results.One possible solution would be for the above code to generate the function a() instead of making 'a' a member. This has its own set of issues, though - suddenly something that looks like a member isn't one, and its address can't be taken, it can't be passed by ref, etc. -- Simenis still invalid andstruct S { int n, m; SomeType!(() => n + m) a; }has another semantics. The latter closures above the current values inside of S. The former has to be constructible without the context, as it is perfectly legal to write struct Outer { struct Inner{} } void main() { auto i = Outer.Inner(); } but would be impossible with this syntax.struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } }
Mar 12 2018
On Monday, 12 March 2018 at 08:59:49 UTC, Alex wrote:An incomplete type is perfectly ok, so there should be no problem with a pointer of the same type inside a struct. If accidentally the pointer refers "this", then it must have been set after construction. As before construction the value of "this"-pointer does not exist and after the construction the pointer to "this" has the default value of null. Therefore, you are aware of this circumstance and appropriate movings of the pointer value are also up to you.But I don't have a hook to update the pointer when the struct is moved. The GC may move my struct without informing me in any way. In fact, even just a copy construction will break this: struct S { S* ptr; this(int dummy) { ptr = &this; } ~this() { // This assert will fail. assert(ptr == &this); } } unittest { S s1 = S(0); S s2 = S(0); assert(&s1 == s1.ptr); assert(&s2 == s2.ptr); s1 = s2; } The reason is copy construction makes a copy[1] of the lhs, then performs the copy construction[2]. If the copy construction[2] succeeds, the copy[1] is destroyed. If not, the copy[1] is blitted back over the original, to give the impression that nothing ever happened. When the destructor is called on the copy[1], &this returns a different address from what it did before, since it's a copy and logically resides at a different address.struct SomeType(alias fn) {} is (or has to be) lowered to something like struct SomeType { typeof(fn)* fn; } Even if fn contains a frame pointer to S it is perfectly legal to have such a type. SomeType would contain a delegate then.Indeed. But stack frames aren't copied or moved the way structs are.However, I think, the syntaxI'm not using any inner structs in my examples. SomeType is assumed to be a separate type that knows nothing about S. If you're saying this should work: unittest { auto tmp = typeof(S.a)(); } It wouldn't, and such code is not possible in D today: struct S { int n; auto a() { return SomeType!(() => n)(); } } struct SomeType(alias fn) { int get() { return fn(); } } unittest { // cannot access frame pointer of foo.S.a.SomeType!(delegate () => this.n).SomeType auto tmp = typeof(S.a())(); } -- Simenis still invalid andstruct S { int n, m; SomeType!(() => n + m) a; }has another semantics. The latter closures above the current values inside of S. The former has to be constructible without the context, as it is perfectly legal to write struct Outer { struct Inner{} } void main() { auto i = Outer.Inner(); } but would be impossible with this syntax.struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } }
Mar 12 2018
On Monday, 12 March 2018 at 09:54:20 UTC, Simen Kjærås wrote:But I don't have a hook to update the pointer when the struct is moved. The GC may move my struct without informing me in any way. In fact, even just a copy construction will break this: struct S { S* ptr; this(int dummy) { ptr = &this; } ~this() { // This assert will fail. assert(ptr == &this); } } unittest { S s1 = S(0); S s2 = S(0); assert(&s1 == s1.ptr); assert(&s2 == s2.ptr); s1 = s2; } The reason is copy construction makes a copy[1] of the lhs, then performs the copy construction[2]. If the copy construction[2] succeeds, the copy[1] is destroyed. If not, the copy[1] is blitted back over the original, to give the impression that nothing ever happened. When the destructor is called on the copy[1], &this returns a different address from what it did before, since it's a copy and logically resides at a different address.Sure, you have. https://dlang.org/spec/struct.html#assign-overload In this case, ref S opAssign(ref S rhs) { return this; } Another point is, that I hope, that pointers don't move anywhere, as in C, by definition.Yes. This is how the structs are meant to be, I thought :)struct SomeType(alias fn) {} is (or has to be) lowered to something like struct SomeType { typeof(fn)* fn; } Even if fn contains a frame pointer to S it is perfectly legal to have such a type. SomeType would contain a delegate then.Indeed. But stack frames aren't copied or moved the way structs are.Ah... I was unclear, I think...However, I think, the syntaxI'm not using any inner structs in my examples. SomeType is assumed to be a separate type that knows nothing about S. If you're saying this should work:is still invalid andstruct S { int n, m; SomeType!(() => n + m) a; }has another semantics. The latter closures above the current values inside of S. The former has to be constructible without the context, as it is perfectly legal to write struct Outer { struct Inner{} } void main() { auto i = Outer.Inner(); } but would be impossible with this syntax.struct S { int n, m; auto a() { return SomeType!(() => n + m)(); } }unittest { auto tmp = typeof(S.a)(); }I thought, this shouldn't be possible (at least in my mind)It wouldn't, and such code is not possible in D today: struct S { int n; auto a() { return SomeType!(() => n)(); } } struct SomeType(alias fn) { int get() { return fn(); } }But this is clearly valid.unittest { // cannot access frame pointer of foo.S.a.SomeType!(delegate () => this.n).SomeType auto tmp = typeof(S.a())(); }For sure, tmp cannot be defined without an instance of S. So the correct unittest in my eyes would be: unittest { S s; auto res = s.a; assert(res.get == S.init.n); }-- Simen
Mar 12 2018
On Monday, 12 March 2018 at 10:37:00 UTC, Alex wrote:Sure, you have. https://dlang.org/spec/struct.html#assign-overload In this case, ref S opAssign(ref S rhs) { return this; }True. Can you fix these, too? struct S { S* ptr; this(int dummy) { ptr = &this; } ~this() { assert(ptr == &this); } } void test(S s) {} unittest { test(S(0)); } S test2() { return S(0); } unittest { S s = test2(); }Another point is, that I hope, that pointers don't move anywhere, as in C, by definition.And why not? D allows for moving garbage collectors.Yes, it's an example of code that works in D today, with similar semantics to those implied in the other example. It's meant to show that just like `auto tmp = typeof(S.a())()` doesn't work today, it shouldn't work with the types used in my other example.unittest { auto tmp = typeof(S.a)(); }I thought, this shouldn't be possible (at least in my mind)It wouldn't, and such code is not possible in D today: struct S { int n; auto a() { return SomeType!(() => n)(); } } struct SomeType(alias fn) { int get() { return fn(); } }But this is clearly valid.I think we may be speaking past one another. Yes, your unittest would be expected to pass. If we go back to the example code: struct S1 { int n, m; SomeType!(() => n + m) a; } vs. struct S2 { int n, m; auto a() { return SomeType!(() => n + m)(); } } I would expect typeof(S1.a) and typeof(S2.a()) to be equivalent. I don't see a good reason why I should be able to construct an instance of typeof(S1.a), since I can't construct a typeof(S2.a()). I see your point that "The latter closures above the current values inside of [S2]". I'm saying I want the former to do the same. I understand that it currently doesn't, and I argue that having it do the same would be a much more useful behavior. Apart from the fact it's impossible, I don't see any good reason not to make it work. :p -- Simenunittest { // cannot access frame pointer of foo.S.a.SomeType!(delegate () => this.n).SomeType auto tmp = typeof(S.a())(); }For sure, tmp cannot be defined without an instance of S. So the correct unittest in my eyes would be: unittest { S s; auto res = s.a; assert(res.get == S.init.n); }
Mar 12 2018
On Monday, 12 March 2018 at 13:04:54 UTC, Simen Kjærås wrote:On Monday, 12 March 2018 at 10:37:00 UTC, Alex wrote:ok... this is a little bit more tricky, as there is no assignment now. :) But in this two cases I assume, you want to have explicit pass by reference, no? struct S { S* ptr; this(int dummy) { ptr = &this; } ~this() { assert(ptr == &this); } } void test(ref S s){} unittest { auto s = S(0); test(s); /* if I call test(S(0)) with void test(S s) as in your example, neither the postblit nor opAssign is called... hm... no idea why... */ } auto test2() { return new S(0); } unittest { auto s = test2(); /* This example differs more from the post before :) test2 is a true factory now, it has to grant the right setting of all members... */ }Sure, you have. https://dlang.org/spec/struct.html#assign-overload In this case, ref S opAssign(ref S rhs) { return this; }True. Can you fix these, too? struct S { S* ptr; this(int dummy) { ptr = &this; } ~this() { assert(ptr == &this); } } void test(S s) {} unittest { test(S(0)); } S test2() { return S(0); } unittest { S s = test2(); }If it were allowed, then "contiguous memory allocation" for arrays would be senseless.Another point is, that I hope, that pointers don't move anywhere, as in C, by definition.And why not? D allows for moving garbage collectors.I see your point too :) But the latter form just grants the existence of an instance of S, whereas the first form doesn't. By the way, this would work: struct S1 { static int n, m; // added a static here. SomeType!(() => n + m) a; } struct SomeType(alias fn){}Yes, it's an example of code that works in D today, with similar semantics to those implied in the other example. It's meant to show that just like `auto tmp = typeof(S.a())()` doesn't work today, it shouldn't work with the types used in my other example.unittest { auto tmp = typeof(S.a)(); }I thought, this shouldn't be possible (at least in my mind)It wouldn't, and such code is not possible in D today: struct S { int n; auto a() { return SomeType!(() => n)(); } } struct SomeType(alias fn) { int get() { return fn(); } }But this is clearly valid.I think we may be speaking past one another. Yes, your unittest would be expected to pass. If we go back to the example code: struct S1 { int n, m; SomeType!(() => n + m) a; } vs. struct S2 { int n, m; auto a() { return SomeType!(() => n + m)(); } } I would expect typeof(S1.a) and typeof(S2.a()) to be equivalent. I don't see a good reason why I should be able to construct an instance of typeof(S1.a), since I can't construct a typeof(S2.a()). I see your point that "The latter closures above the current values inside of [S2]". I'm saying I want the former to do the same. I understand that it currently doesn't, and I argue that having it do the same would be a much more useful behavior. Apart from the fact it's impossible, I don't see any good reason not to make it work. :punittest { // cannot access frame pointer of foo.S.a.SomeType!(delegate () => this.n).SomeType auto tmp = typeof(S.a())(); }For sure, tmp cannot be defined without an instance of S. So the correct unittest in my eyes would be: unittest { S s; auto res = s.a; assert(res.get == S.init.n); }-- Simen
Mar 12 2018