www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Instantiation of nested structs should be allowed outside their parent

reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
Just make it ` system`, and even then ONLY if something is 
actually getting instantiated and it's not just a CTFE test. 
Unless I'm missing something, that should be the behavior. The 
'cannot access frame pointer' blah SO needs to go away...

Rationale: instantiating things is super useful for compile time 
tests and inference. Nested structs complicate that to wazoo with 
that failure.

If you're not convinced, I encourage you to try and define e.g.

```d
enum bool isNothrowCopyable(To, From) = /* ??? */
```

without reimplementing copy initialization by hand. It should be 
a trivial test. Just test copy-initialization of a union field. 
But no. Not when nested structs are involved. And they can get 
involved indirectly.
Nov 07 2021
next sibling parent reply Stefan Koch <uplink.coder googlemail.com> writes:
On Monday, 8 November 2021 at 01:02:11 UTC, Stanislav Blinov 
wrote:
 Just make it ` system`, and even then ONLY if something is 
 actually getting instantiated and it's not just a CTFE test. 
 Unless I'm missing something, that should be the behavior. The 
 'cannot access frame pointer' blah SO needs to go away...

 Rationale: instantiating things is super useful for compile 
 time tests and inference. Nested structs complicate that to 
 wazoo with that failure.

 If you're not convinced, I encourage you to try and define e.g.

 ```d
 enum bool isNothrowCopyable(To, From) = /* ??? */
 ```

 without reimplementing copy initialization by hand. It should 
 be a trivial test. Just test copy-initialization of a union 
 field. But no. Not when nested structs are involved. And they 
 can get involved indirectly.
How would you want a struct to behave when it is used outside of it's context? Set the context pointer to null and throw upon access?
Nov 08 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 01:06:16 UTC, Stefan Koch wrote:

 How would you want a struct to behave when it is used outside 
 of it's context?
 Set the context pointer to null and throw upon access?
Throw? No, segfault like any other null dereference. Ain't no harm in that. As compared to absolutely ruining generic code for, frankly, no benefit, and numerous, NUMEROUS bugs and "workarounds" that simply don't need to exist.
Nov 08 2021
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 9 November 2021 at 01:54:44 UTC, Stanislav Blinov 
wrote:
 Throw? No, segfault like any other null dereference. Ain't no 
 harm in that. As compared to absolutely ruining generic code 
 for, frankly, no benefit, and numerous, NUMEROUS bugs and 
 "workarounds" that simply don't need to exist.
I'd rather have compiler throw errors rather than having yet another potential segfault in application which needs to be checked.
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 12:59:08 UTC, Alexandru Ermicioi 
wrote:

 I'd rather have compiler throw errors rather than having yet 
 another potential segfault in application which needs to be 
 checked.
In that case compilation of 99% of D programs on Unix has to fail immediately. Because, potentially, they can all segfault before even reaching _Dmain. Only those that don't in any way link to C runtime may compile freely. Sound silly enough?
Nov 09 2021
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 9 November 2021 at 15:39:49 UTC, Stanislav Blinov 
wrote:
 On Tuesday, 9 November 2021 at 12:59:08 UTC, Alexandru Ermicioi 
 wrote:

 I'd rather have compiler throw errors rather than having yet 
 another potential segfault in application which needs to be 
 checked.
In that case compilation of 99% of D programs on Unix has to fail immediately. Because, potentially, they can all segfault before even reaching _Dmain. Only those that don't in any way link to C runtime may compile freely. Sound silly enough?
If compiler can help in preventing potential bugs, then it should do that, given it is in scope of what it handles. C runtime is not handled by it, and therefore it can only make assumptions that is working correctly just like any other already compiled library that is being used by the source code that is compiled right now. The problem from discussion in this thread seems to be due to bugs in how copy constructor is implemented, therefore first thing would be best to do, is to file a bug report. After this you can try fix it yourself, wait for other person do it, or motivate people in doing it. Imho this is not a minor thing, and should be fixed quite fast. In meantime, you could try make inner struct static. This should eliminate the context pointer. Best regards, Alexandru
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 18:35:44 UTC, Alexandru Ermicioi 
wrote:

 If compiler can help in preventing potential bugs...
That's just the thing. That error message is, plain and simple, handwaving. Unless, like I said already, I'm missing something. The compiler is perfectly happy with me initializing a nested struct to its .init value. But instantiating it elsewhere - nuuuh, that's not allowed!
 The problem from discussion in this thread seems to be due to 
 bugs in how copy constructor is implemented, therefore first 
 thing would be best to do, is to file a bug report. After this 
 you can try fix it yourself, wait for other person do it, or 
 motivate people in doing it.
Please, this is not helpful. There are already reports on this, including ones filed by myself. Why do you think I created this topic?
 Imho this is not a minor thing, and should be fixed quite fast. 
 In meantime, you could try make inner struct static. This 
 should eliminate the context pointer.
That is not "inner" struct. That is nested struct. And the whole point of this topic *is* nested structs. I don't need to eliminate context pointer. I need generic code to work with nested structs. Including the copy tests being discussed here. Including, for gods sake, Phobos ranges.
Nov 09 2021
parent reply Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 9 November 2021 at 19:00:38 UTC, Stanislav Blinov 
wrote:
 On Tuesday, 9 November 2021 at 18:35:44 UTC, Alexandru Ermicioi 
 wrote:

 If compiler can help in preventing potential bugs...
That's just the thing. That error message is, plain and simple, handwaving. Unless, like I said already, I'm missing something. The compiler is perfectly happy with me initializing a nested struct to its .init value. But instantiating it elsewhere - nuuuh, that's not allowed!
.init field itself has lots of edge cases that are not well thought, and you might just be hitting one of them. Per my understanding (which is not necessarily right, please correct if it), the .init of a nested struct should have the context pointer of the method it is called in setup. I.e. .init for such structs would be possible to use only in the scope of parent, be it class, or function body.
 Please, this is not helpful. There are already reports on this, 
 including ones filed by myself. Why do you think I created this 
 topic?
Wasn't aware that they were there already, hence recommended to be filed.
 That is not "inner" struct. That is nested struct. And the 
 whole point of this topic *is* nested structs. I don't need to 
 eliminate context pointer. I need generic code to work with 
 nested structs. Including the copy tests being discussed here. 
 Including, for gods sake, Phobos ranges.
I was referring to nested structs. Inner or nested structs are the same thing, they are inside or nested into something. The only current solution I'd see to avoid this issue, would be to first static check if the type is nested type, and just use hasMember trait to check that a `__ctor` with one argument of same type as the structure itself is present. If so, most probably it is a copy constructor, and hence you can return true. I.e. check the signature of constructor, instead of actually trying to instantiate the nested struct inside is expression or `__traits` compiles expression. That should work even with disabled copy constructors, per my knowledge. I know, that sometimes D meta-programming is pain in some ... place due to bugs, and not well rounded edge cases, but the solution you proposed to fix that (have context pointer null), is a thing which is not better than the current language spec, in my opinion. So for now best is to try to convince people working on compiler that the issue is quite serious and needs working on (guess this thread itself), while in meantime, you may try use the solution I've proposed to do the checks. Best regards, Alexandru.
Nov 09 2021
next sibling parent Alexandru Ermicioi <alexandru.ermicioi gmail.com> writes:
On Tuesday, 9 November 2021 at 22:22:20 UTC, Alexandru Ermicioi 
wrote:
 ...

 Best regards,
 Alexandru.
You may also try iterate over all constructor overloads, and check for function signature with is expression. This should not force the compiler to instantiate the struct (and hence complain for missing context pointer), and achieve what you need in case for copy constructor.
Nov 09 2021
prev sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 22:22:20 UTC, Alexandru Ermicioi 
wrote:

 if it), the .init of a nested struct should have the context 
 pointer of the method it is called in setup.
No. .init of a nested struct just contains a null pointer in the .tupleof[$-1]. Context does not exist until you instantiate a nested struct within the parent scope. That's when an actual stack frame gets allocated. Consequently, doing things like T a = T.init; when T is a nested struct is exactly the kind of "bug" you're referring to. And the compiler is perfectly happy with that. Because, actually, that is not a bug at all. Just like leaving raw pointers null-initialized.
 The only current solution I'd see to avoid this issue, would be 
 to first static check if the type is nested type, and just use 
 hasMember trait to check that a `__ctor` with one argument of 
 same type as the structure itself is present.
There is a dedicated trait that shows presence of copy constructor. BTW, hasMember is the WORST method of checking for functions. Because of overloads, and because of templates. IMHO, it's only good for checking for the presence of a destructor, and that's about it. Please understand, I am not asking "how to work around this" here. I know how, I've already shown how. That is not what I want to be doing. I want the opposite - to not have to be doing that.
 If so, most probably it is a copy constructor, and hence you 
 can return true.
No, you can't. The ctor might be templated, so won't even exist unless you try to instantiate it. Or there might not be any copy constructor at all. Nor postblit. But the thing may simply not be copyable (i.e. const pointer into a pointer), hence the trait must return false.
Nov 09 2021
prev sibling next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Monday, 8 November 2021 at 01:02:11 UTC, Stanislav Blinov 
wrote:
 Just make it ` system`...
On second thought, doesn't even need to be system. Just needs to be allowed.
Nov 08 2021
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 08.11.21 02:02, Stanislav Blinov wrote:
 
 If you're not convinced, I encourage you to try and define e.g.
 
 ```d
 enum bool isNothrowCopyable(To, From) = /* ??? */
 ```
There is never really a need to instantiate a value of any type just to check what it supports. Just use parameters in your function literal. This has the side effect of supporting `inout` shenanigans as well. enum bool isNothrowCopyable(To, From) = __traits(compiles, (To to, From from)nothrow{ to=from; }); (Untested. If I have misunderstood what it should check for, just change the function body.)
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 09:24:29 UTC, Timon Gehr wrote:
 On 08.11.21 02:02, Stanislav Blinov wrote:
 
 If you're not convinced, I encourage you to try and define e.g.
 
 ```d
 enum bool isNothrowCopyable(To, From) = /* ??? */
 ```
...WITHOUT REIMPLEMENTING COPY INITIALIZATION BY HAND.
 There is never really a need to instantiate a value of any type 
 just to check what it supports.
Yes, there is. isInputRange tries, and fails for some nested structs (keyword - some). This copy attributes check needs an instance too.
 enum bool isNothrowCopyable(To, From) = __traits(compiles, (To 
 to, From from)nothrow{ to=from; });
Nope. That tests assignment. Which may, or may not, also test destructor, depending on whether opAssign is defined. The check must needs to test ONLY copy-initialization (blit, copy ctor, and/or postblit).
 (Untested. If I have misunderstood what it should check for, 
 just change the function body.)
Yes. "Just". For that specific check - by recreating the copy semantics. I can show you the code I came up with, but like I said, I really do encourage you to try. Just to experience, in full, how ridiculous it gets.
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 15:11, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 09:24:29 UTC, Timon Gehr wrote:
 ...

 There is never really a need to instantiate a value of any type just 
 to check what it supports.
Yes, there is. isInputRange tries, and fails for some nested structs (keyword - some).
Fair, sometimes you want to check whether you can declare a value of the given type, though that _should_ fail in case it's not actually possible for the algorithm to do that and get an actually working instance. It's plausible that that check does not actually work correctly for some Phobos functions though, as local instantiation of templates is a bit messy.
 This copy attributes check needs an instance too.
  >> enum bool isNothrowCopyable(To, From) = __traits(compiles, (To to,
 From from)nothrow{ to=from; });
Nope. That tests assignment. Which may, or may not, also test destructor, depending on whether opAssign is defined. The check must needs to test ONLY copy-initialization (blit, copy ctor, and/or postblit). ...
I see, I guess you actually want this, but it does not work: enum bool isNothrowCopyable(To, From) = __traits(compiles, (From from)nothrow{ To to=from; }); What's the use case for checking only this kind of initialization, if you don't need to create new instances of `to`?
Nov 09 2021
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:07, Timon Gehr wrote:
 
 I see, I guess you actually want this, but it does not work:
 
 enum bool isNothrowCopyable(To, From) = __traits(compiles, (From 
 from)nothrow{ To to=from; });
Well, that checks the destructor too of course. x) But now I understand why you want to instantiate To.
Nov 09 2021
prev sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 15:07:58 UTC, Timon Gehr wrote:
 On 09.11.21 15:11, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 09:24:29 UTC, Timon Gehr wrote:
 ...

 There is never really a need to instantiate a value of any 
 type just to check what it supports.
Yes, there is. isInputRange tries, and fails for some nested structs (keyword - some).
Fair, sometimes you want to check whether you can declare a value of the given type, though that _should_ fail in case it's not actually possible for the algorithm to do that and get an actually working instance. It's plausible that that check does not actually work correctly for some Phobos functions though, as local instantiation of templates is a bit messy.
The point is it must be possible to test that, and it's not, because of some arbitrary artificial limitation. I mean, we can freely instantiate null delegates, but we can't instantiate nested structs? There's, to me, zero logic in that.
 I see, I guess you actually want this, but it does not work:

 enum bool isNothrowCopyable(To, From) = __traits(compiles, 
 (From from)nothrow{ To to=from; });
No, that also tests destruction, of both `from`, because it's D, where functions destruct their arguments, and `to`, because it's a local :) What I want is this: ```d template isNothrowCopyable(To, From) if (is(immutable To == immutable From)) { enum bool isNothrowCopyable = is(typeof((ref scope From from) nothrow { union U { To to; } U u = U(from); })); } ``` ...but it indeed doesn't work for nested structs. And a thing that does "work" has to reimplement compiler's copy semantics, including field-by-field blitting, calling constructors and postblits, etc.
 What's the use case for checking only this kind of 
 initialization, if you don't need to create new instances of 
 `to`?
? You *do* need to create new instances of `to`. By copying existing instances. Any container that wants to support copying has to do that, and beyond. I want a test that would also tell me if my container needs special handling of exception guarantees, or if it can freely do an easy thing and not worry about it: can I just copy existing things on element insertion one element over, or do I have to do a three step algorithm because copying might throw?.. I also want other tests, that would tell me if copying is safe to do, if it needs GC, if it's pure... (I *do* have them already, but like I said, they're ludicrous, for no other reason than this handwaving error message). And as for isInputRange - I haven't got a clue if it's even possible to make it work for all nested structs unless the compiler stops issuing this error message.
Nov 09 2021
next sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:30, Stanislav Blinov wrote:
 
 ```d
 template isNothrowCopyable(To, From)
 if (is(immutable To == immutable From))
 {
      enum bool isNothrowCopyable = is(typeof((ref scope From from) 
 nothrow { union U { To to; } U u = U(from); }));
 }
 ```
 
 ...but it indeed doesn't work for nested structs. And a thing that does 
 "work" has to reimplement compiler's copy semantics, including 
 field-by-field blitting, calling constructors and postblits, etc.
template isNothrowCopyable(To, From) if (is(immutable To == immutable From)) { enum bool isNothrowCopyable = is(typeof((ref scope From from) nothrow { union U { auto to=To.init; } U u = U(from); })); }
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:36, Timon Gehr wrote:
 On 09.11.21 16:30, Stanislav Blinov wrote:
 ```d
 template isNothrowCopyable(To, From)
 if (is(immutable To == immutable From))
 {
      enum bool isNothrowCopyable = is(typeof((ref scope From from) 
 nothrow { union U { To to; } U u = U(from); }));
 }
 ```

 ...but it indeed doesn't work for nested structs. And a thing that 
 does "work" has to reimplement compiler's copy semantics, including 
 field-by-field blitting, calling constructors and postblits, etc.
template isNothrowCopyable(To, From) if (is(immutable To == immutable From)) {     enum bool isNothrowCopyable = is(typeof((ref scope From from) nothrow { union U { auto to=To.init; } U u = U(from); })); }
(I don't know if that solves your problem, as I don't actually have an example where your implementation does not work.)
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 15:39:48 UTC, Timon Gehr wrote:

 template isNothrowCopyable(To, From)
 if (is(immutable To == immutable From))
 {
      enum bool isNothrowCopyable = is(typeof((ref scope From 
 from) nothrow { union U { auto to=To.init; } U u = U(from); 
 }));
 }
(I don't know if that solves your problem, as I don't actually have an example where your implementation does not work.)
```d template isNothrowCopyable(To, From = To) if (is(immutable To == immutable From)) { enum bool isNothrowCopyable = is(typeof((ref scope From from) nothrow { union U { auto to=To.init; } U u = U(from); })); } unittest { int dtors; struct NestedThatPasses { ~this() { ++dtors; } } struct NestedThatFails { this(return ref scope typeof(this)) nothrow {} ~this() { ++dtors; } } static assert(isNothrowCopyable!NestedThatPasses); static assert(isNothrowCopyable!NestedThatFails); } ```
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:44, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 15:39:48 UTC, Timon Gehr wrote:
 
 template isNothrowCopyable(To, From)
 if (is(immutable To == immutable From))
 {
      enum bool isNothrowCopyable = is(typeof((ref scope From from) 
 nothrow { union U { auto to=To.init; } U u = U(from); }));
 }
(I don't know if that solves your problem, as I don't actually have an example where your implementation does not work.)
```d template isNothrowCopyable(To, From = To) if (is(immutable To == immutable From)) {     enum bool isNothrowCopyable = is(typeof((ref scope From from) nothrow { union U { auto to=To.init; } U u = U(from); })); } unittest {     int dtors;     struct NestedThatPasses     {         ~this() { ++dtors; }     }     struct NestedThatFails     {         this(return ref scope typeof(this)) nothrow {}         ~this() { ++dtors; }     }     static assert(isNothrowCopyable!NestedThatPasses);     static assert(isNothrowCopyable!NestedThatFails); } ```
It indeed seems ridiculous that you can't copy a nested struct using its copy constructor. I guess one solution would be to use the frame pointer of the instance you are about to copy to call the copy constructor. I don't think calling it with `null` is a great idea.
Nov 09 2021
next sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 15:55:14 UTC, Timon Gehr wrote:

 It indeed seems ridiculous that you can't copy a nested struct 
 using its copy constructor. I guess one solution would be to 
 use the frame pointer of the instance you are about to copy to 
 call the copy constructor.

 I don't think calling it with `null` is a great idea.
Thing is you're not calling it with null. AFAIK, the compiler blits the context first, then calls the ctor. Pretty much the only way to crash with nested structs is to either use a default-initialized value, or do some very unsafe things. And even then - only if you actually call methods that do access the context. Which not all of them have to do. ...and then we get to even more "interesting" unittests, where a struct contains an instance of a nested struct...
Nov 09 2021
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:59, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 15:55:14 UTC, Timon Gehr wrote:
 
 It indeed seems ridiculous that you can't copy a nested struct using 
 its copy constructor. I guess one solution would be to use the frame 
 pointer of the instance you are about to copy to call the copy 
 constructor.

 I don't think calling it with `null` is a great idea.
Thing is you're not calling it with null. AFAIK, the compiler blits the context first, then calls the ctor.
Well, that's what it should do, but it seems what actually happens is that compilation fails instead because the compiler thinks it needs a context from somewhere else to create a new instance. This seems like a bug to me.
Nov 09 2021
prev sibling parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 15:55:14 UTC, Timon Gehr wrote:

 I don't think calling it with `null` is a great idea.
Oh and, of course, this type of compile-time tests aren't actually calling anything at all anyway.
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 17:04, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 15:55:14 UTC, Timon Gehr wrote:
 
 I don't think calling it with `null` is a great idea.
Oh and, of course, this type of compile-time tests aren't actually calling anything at all anyway.
Presumably you are testing because you want to actually do it later.
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 16:06:02 UTC, Timon Gehr wrote:
 On 09.11.21 17:04, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 15:55:14 UTC, Timon Gehr wrote:
 
 I don't think calling it with `null` is a great idea.
Oh and, of course, this type of compile-time tests aren't actually calling anything at all anyway.
Presumably you are testing because you want to actually do it later.
No, I am testing because I want to copy things :) Or have ranges of them. The NestedThatFails from before. Array of that is not an input range, according to Phobos. Because it fails one of the checks. Because the compiler is being overly cautious. Which, again, makes NO sense. Nested struct is, effectively, just a family of delegates packed together in one type. There's zero reason not to let it be instantiated in some specific cases. Or then we have to forbid instantiation of ALL delegates unless we're providing context. Thus making delegates at least 50% useless.
Nov 09 2021
next sibling parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 16:19:04 UTC, Stanislav Blinov 
wrote:

 just a family of delegates packed together in one type.
Should've said "closures", but whatever.
Nov 09 2021
prev sibling parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 17:19, Stanislav Blinov wrote:
 Presumably you are testing because you want to actually do it later.
No, I am testing because I want to copy things :)
Sure, which is what I said (do it = copy things). But why would you want the check to behave differently from actual code that copies things? (As I have argued in the other post, both the check and actual code should just succeed.)
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 16:34:26 UTC, Timon Gehr wrote:

 Sure, which is what I said (do it = copy things). But why would 
 you want the check to behave differently from actual code that 
 copies things?
? Because the check only needs to tell me if copying throws or not. That specific check, that is. Nothing else. Because that information drives how I allocate or how, exactly, do I copy things. Other checks drive other things. BTW, I forgot to show one more thing, that is that isNothrowCopyable reduces to isCopyable for BetterC. Not Phobos' isCopyable, but an isCopyable that tests distinct types because qualifier hell. Which should be that same union test from before, only without attributes. But, alas, it is not. For nested structs, such checks need to do things outside of context - that's why they MAY behave differently from actual code. Compiler doesn't like that. I think it shouldn't not like that.
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 17:51, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 16:34:26 UTC, Timon Gehr wrote:
 
 Sure, which is what I said (do it = copy things). But why would you 
 want the check to behave differently from actual code that copies things?
? Because the check only needs to tell me if copying throws or not.
For the specific issue we have been discussing, it seems to me that copying does not work at all, independent of whether it's throwing or not. It should just work.
 That 
 specific check, that is. Nothing else. Because that information drives 
 how I allocate or how, exactly, do I copy things. Other checks drive 
 other things. BTW, I forgot to show one more thing, that is that 
 isNothrowCopyable reduces to isCopyable for BetterC. Not Phobos' 
 isCopyable, but an isCopyable that tests distinct types because 
 qualifier hell. Which should be that same union test from before, only 
 without attributes. But, alas, it is not.
 ...
What's different?
 For nested structs, such checks need to do things outside of context - 
 that's why they MAY behave differently from actual code. Compiler 
 doesn't like that. I think it shouldn't not like that.
Why should the checks be able to do anything that standard code cannot do? Why should the checks not have access to context when the actual code will have such access? I think a lot of the trouble you are having probably comes down to compiler bugs and/or missing reasonable enhancement requests.
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 17:18:31 UTC, Timon Gehr wrote:
 On 09.11.21 17:51, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 16:34:26 UTC, Timon Gehr wrote:
 
 Sure, which is what I said (do it = copy things). But why 
 would you want the check to behave differently from actual 
 code that copies things?
? Because the check only needs to tell me if copying throws or not.
For the specific issue we have been discussing, it seems to me that copying does not work at all, independent of whether it's throwing or not. It should just work.
It does work just fine. Can copy NestedThatFails within its context. Can copy it to outside of its context too if need be, with copyEmplace. Before you ask "why not just use copyEmplace for the test then?" the answers are 2: 1) it's ' system', so I won't be able to devise an isSafe... test out of it, and 2) look at its source code, the very first static if inside ;)
 That specific check, that is. Nothing else. Because that 
 information drives how I allocate or how, exactly, do I copy 
 things. Other checks drive other things. BTW, I forgot to show 
 one more thing, that is that isNothrowCopyable reduces to 
 isCopyable for BetterC. Not Phobos' isCopyable, but an 
 isCopyable that tests distinct types because qualifier hell. 
 Which should be that same union test from before, only without 
 attributes. But, alas, it is not.
 ...
What's different?
I don't understand what you're asking. What's different where?
 Why should the checks be able to do anything that standard code 
 cannot do? Why should the checks not have access to context 
 when the actual code will have such access? I think a lot of 
 the trouble you are having probably comes down to compiler bugs 
 and/or missing reasonable enhancement requests.
You've seen the test. It's a lambda outside of unittest. How do you propose to write a trait that DOES have access to context? Same goes for std.range.isInputRange... Just to reiterate - I *have* a working implementation of a test that works around this problem for testing copy initialization specifically. I'm just tired of these workarounds. ```d struct Container(T) { import core.memory : pureMalloc, pureFree; T* storage; this(T x) { // Make known to GC, yadda yadda, skipped for brevity import core.lifetime : moveEmplace; storage = cast(T*) pureMalloc(T.sizeof); moveEmplace(x, *storage); } this(ref return scope typeof(this) other) { // This is where I'd use an isNothrowCopyable trait. // If T is nothrow-copyable, I'd just allocate and copy all things. // If it isn't, I'll need an allocation guard and a copy algorithm // that tracks success and destructs already copied things if a given copy fails. import core.lifetime : copyEmplace; storage = cast(T*) pureMalloc(T.sizeof); copyEmplace(*other.storage, *storage); } ~this() nothrow { if (storage) { static if (__traits(hasMember, T, "__xdtor")) storage.__xdtor; pureFree(storage); } } } unittest { int dtors; struct NestedThatFails { this(return ref scope typeof(this)) nothrow {} ~this() nothrow { ++dtors; } } { NestedThatFails y; () nothrow { Container!NestedThatFails container = y; // copies just fine auto copied = container; // copies just fine } (); } // y, the copy passed to `container`, the value in `container`, the value in `copied`, // should be 4 dtors (well, ideally 3, but that's only after move semantics DIP is implemented). assert(dtors == 4); // But according to the check, it's not nothrow-copyable! static assert(isNothrowCopyable!NestedThatFails); } ```
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 19:41, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 17:18:31 UTC, Timon Gehr wrote:
 On 09.11.21 17:51, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 16:34:26 UTC, Timon Gehr wrote:

 Sure, which is what I said (do it = copy things). But why would you 
 want the check to behave differently from actual code that copies 
 things?
? Because the check only needs to tell me if copying throws or not.
For the specific issue we have been discussing, it seems to me that copying does not work at all, independent of whether it's throwing or not. It should just work.
It does work just fine. Can copy NestedThatFails within its context. Can copy it to outside of its context too if need be, with copyEmplace. Before you ask "why not just use copyEmplace for the test then?" the answers are 2: 1) it's ' system', so I won't be able to devise an isSafe... test out of it, and
Also, you won't be able to use it in ` safe` code. That's not the same as "just work". The compiler should just do this correctly, just like copyEmplace.
 2) look at its source code, the very first static if inside ;)
 
 That specific check, that is. Nothing else. Because that information 
 drives how I allocate or how, exactly, do I copy things. Other checks 
 drive other things. BTW, I forgot to show one more thing, that is 
 that isNothrowCopyable reduces to isCopyable for BetterC. Not Phobos' 
 isCopyable, but an isCopyable that tests distinct types because 
 qualifier hell. Which should be that same union test from before, 
 only without attributes. But, alas, it is not.
 ...
What's different?
I don't understand what you're asking. What's different where? ...
You said: "should be _that same_ union test [...] it is not". My question was what is not the same.
 Why should the checks be able to do anything that standard code cannot 
 do? Why should the checks not have access to context when the actual 
 code will have such access? I think a lot of the trouble you are 
 having probably comes down to compiler bugs and/or missing reasonable 
 enhancement requests.
You've seen the test. It's a lambda outside of unittest. How do you propose to write a trait that DOES have access to context? Same goes for std.range.isInputRange... ...
The constraint should have as much access to context as the function itself does. E.g., templates are sometimes instantiated locally in the caller's context.
 Just to reiterate - I *have* a working implementation of a test that 
 works around this problem for testing copy initialization specifically.
Well, that specific problem comes down to what I think is just a compiler bug, I was just suggesting that probably there are other things that need to be fixed in the compiler as well.
 I'm just tired of these workarounds.
 ...
As you should be. It's not an acceptable situation.
Nov 09 2021
parent reply Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 21:08:08 UTC, Timon Gehr wrote:
 On 09.11.21 19:41, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 17:18:31 UTC, Timon Gehr wrote:
 On 09.11.21 17:51, Stanislav Blinov wrote:
 On Tuesday, 9 November 2021 at 16:34:26 UTC, Timon Gehr 
 wrote:

 Sure, which is what I said (do it = copy things). But why 
 would you want the check to behave differently from actual 
 code that copies things?
? Because the check only needs to tell me if copying throws or not.
For the specific issue we have been discussing, it seems to me that copying does not work at all, independent of whether it's throwing or not. It should just work.
It does work just fine. Can copy NestedThatFails within its context. Can copy it to outside of its context too if need be, with copyEmplace. Before you ask "why not just use copyEmplace for the test then?" the answers are 2: 1) it's ' system', so I won't be able to devise an isSafe... test out of it, and
Also, you won't be able to use it in ` safe` code. That's not the same as "just work". The compiler should just do this correctly, just like copyEmplace.
You can use it in safe code. If you can determine that the copy is actually safe, and you're not corrupting anyone's memory. Just like the ctor from my example in the previous post.
 You said: "should be _that same_ union test [...] it is not". 
 My question was what is not the same.
I meant that currently it can't be "that same union test only without the attributes" because it will also fail, i.e. it will evaluate to false for copyable structs, for the same reasons. So, alas, it has to be a workaround monstrosity.
 You've seen the test. It's a lambda outside of unittest. How 
 do you propose to write a trait that DOES have access to 
 context? Same goes for std.range.isInputRange...
 ...
 The constraint should have as much access to context as the 
 function itself does. E.g., templates are sometimes 
 instantiated locally in the caller's context.
That won't help. Look at the example from my previous post. Container is nowhere near that context, and can't be. But it needs to be able to get *useful* introspection out of T. It won't if the compiler bails artificially.
 Just to reiterate - I *have* a working implementation of a 
 test that works around this problem for testing copy 
 initialization specifically.
For reference: ```d enum bool isCopyable(T, From=T) = is(typeof(inferCopyAttributesIfCopyable!(T,From))); enum bool isSafeCopyable(T, From=T) = is(typeof(() safe => inferCopyAttributesIfCopyable!(T,From))); enum bool isNogcCopyable(T, From=T) = is(typeof(() nogc => inferCopyAttributesIfCopyable!(T,From))); version (D_BetterC) { enum bool isNothrowCopyable(T, From=T) = .isCopyable!(T, From); } else { enum bool isNothrowCopyable(T, From=T) = is(typeof(() nothrow => inferCopyAttributesIfCopyable!(T,From))); } void inferIfBlittable(To, From)(scope To* to = null, scope From* from = null) if (is(immutable To == immutable From)) { static if (__traits(isStaticArray, To)) inferIfBlittable(&(*to)[0], &(*from)[0]); else static if (is(To == struct)) { foreach (i, ref it; to.tupleof[0 .. $-__traits(isNested, To)]) { inferIfBlittable(&it, &from.tupleof[i]); } } else { To copied = *from; } } void inferCopyAttributesIfCopyable(To, From)(scope To* to = null, scope From* from = null) if (is(immutable To == immutable From)) { static if (__traits(isStaticArray, To)) inferCopyAttributesIfCopyable(&(*to)[0], &(*from)[0]); else static if (is(To == struct)) { // This all SOOOO does not need to exist... // Unfortunately, if the struct is nested, doing a simple test // would result in "cannot access frame pointer" blah blah, // and if a nested struct is a field in some other struct, well, // that's even more "pleasant"... static if (__traits(hasPostblit, To)) { inferIfBlittable(to, from); to.__xpostblit; } else static if (__traits(hasCopyConstructor, To)) { to.__ctor(*from); } else { inferIfBlittable(to, from); } } else { To copied = *from; } } ``` I mean, seriously, what the what! :D
Nov 09 2021
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 22:45, Stanislav Blinov wrote:
 
 That won't help.
It will help in some cases. What will help with the specific issue you have shown was already explained in the first part of my post. I.e., the compiler needs fixing. https://issues.dlang.org/show_bug.cgi?id=22499 Of course, this was just one example. Again: I am pretty sure that if you explain other pain points we will find more bugs in the compiler.
Nov 09 2021
parent Stanislav Blinov <stanislav.blinov gmail.com> writes:
On Tuesday, 9 November 2021 at 22:20:35 UTC, Timon Gehr wrote:
 On 09.11.21 22:45, Stanislav Blinov wrote:
 
 That won't help.
It will help in some cases. What will help with the specific issue you have shown was already explained in the first part of my post. I.e., the compiler needs fixing.
Yes, it does :) But I think instead of "fixing" that error message it simply needs to be thrown out. It's not doing anything useful, and plenty harmful.
 https://issues.dlang.org/show_bug.cgi?id=22499

 Of course, this was just one example. Again: I am pretty sure 
 that if you explain other pain points we will find more bugs in 
 the compiler.
I believe that would be the same as https://issues.dlang.org/show_bug.cgi?id=22446 There are also some other nested-related things. https://issues.dlang.org/show_bug.cgi?id=22434 https://issues.dlang.org/show_bug.cgi?id=19375
Nov 09 2021
prev sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
On 09.11.21 16:30, Stanislav Blinov wrote:
 I mean, we can freely instantiate null delegates, but we can't 
 instantiate nested structs?
You can get the "null" instance: auto foo(int x){ struct S{ int t(){ return x; } } return S(); } void main(){ // typeof(foo(0)) s; // error auto s=typeof(foo(0)).init; // ok }
Nov 09 2021