digitalmars.D - Something like ADL from C++?
- Manu (76/76) Dec 03 Maybe someone has a pattern for doing this kind of thing...
- Max Samukha (67/73) Dec 03 You could merge overloads like this:
- Max Samukha (3/5) Dec 03 The static import is a leftover from a previous iteration. You
- Walter Bright (10/11) Dec 03 That's because static foreach will become fairly useless if it introduce...
- Timon Gehr (29/44) Dec 03 It does introduce a scope for the loop variables, there is just no way
- Richard (Rikki) Andrew Cattermole (6/9) Dec 03 I'm trying to come up with alternative syntax.
- Walter Bright (1/1) Dec 03 Thanks for the clarification
- Bradley Chatha (94/97) Dec 03 I gave this go using an eponymous template - it's definitely a
- Max Samukha (4/17) Dec 03 BTW, do you really need to create the overload set? You could
- Richard (Rikki) Andrew Cattermole (6/6) Dec 03 This won't help you today but:
- Derek Fawcus (18/24) Dec 03 Please no. Than then also makes the betterC subset too complex.
- Richard (Rikki) Andrew Cattermole (16/44) Dec 03 My ideas post:
- Alexandru Ermicioi (6/9) Dec 03 as a compromise you could have an uda that packs custom
- Derek Fawcus (21/29) Dec 05 That is essentially what Go does for its serialisation (Marshal
- Richard (Rikki) Andrew Cattermole (33/35) Dec 05 I said this over in ideas, but the problem isn't controlling
- Alexandru Ermicioi (7/12) Dec 05 I'm confused for example. What is the problem you're mentioning
- Richard (Rikki) Andrew Cattermole (21/38) Dec 05 We do not have runtime reflection. We cannot copy languages that do,
- Sebastiaan Koppe (3/6) Dec 05 There is the curious recurring template pattern of course.
- Arafel (21/44) Dec 05 ```d
- Richard (Rikki) Andrew Cattermole (12/63) Dec 06 I'm fully aware of template this parameters, I was trying to get them to...
- Arafel (68/82) Dec 06 That's true, I found a workaround using constructors and registering
- Richard (Rikki) Andrew Cattermole (7/26) Dec 06 Yes, once you have a level in the hierarchy that adds a constructor, you...
- Arafel (4/7) Dec 06 Shared static constructors with a template this parameter would be the
- Richard (Rikki) Andrew Cattermole (6/14) Dec 06 For deserialization sure.
- Walter Bright (2/2) Dec 03 Koenig lookup? I ran away, far away, from that, after implementing it fo...
- Richard (Rikki) Andrew Cattermole (5/9) Dec 03 I got a bit of a chuckle out of that.
- Timon Gehr (3/7) Dec 03 Sure, but the question was, how do you do actually do the serialization
- Walter Bright (4/6) Dec 03 As I haven't attempted that, I might suggest use of:
- Richard (Rikki) Andrew Cattermole (3/12) Dec 03 Don't forget template this parameter!
- Kagamin (3/5) Dec 04 C# does custom serialization by inheritance of
- Kagamin (26/26) Dec 04 ```
- Walter Bright (3/3) Dec 03 Back before I ended work on C++, a number of C++ Illuminati told me priv...
- Richard (Rikki) Andrew Cattermole (12/15) Dec 03 https://en.cppreference.com/w/cpp/language/adl
- Manu (10/15) Dec 04 I've used it effectively for decades and never had any problems.
- Manu (8/11) Dec 04 So what do you suggest?
- Jonathan M Davis (21/33) Dec 05 The way that I've seen serialization done in D is to use UDAs, and the
- Alexandru Ermicioi (5/10) Dec 05 Java serialisation libs still do employ annotations(UDA) for
- Timon Gehr (29/30) Dec 03 There are a couple solutions that look okay already, but basically,
- Manu (6/36) Dec 04 "Unordered scope"; it never occurred to me that was a concept! But I can
- Walter Bright (5/7) Dec 11 I don't know if anyone remembers, but there was a huge debate about this...
- Timon Gehr (5/15) Dec 12 I don't think I was around for that, but to be fair, it is unlikely that...
Maybe someone has a pattern for doing this kind of thing... So, I have a function that does something to a various set of things; let's say we're going to serialise them or something: ``` module serialise; void serialise(T)(void[] buffer, T) if (isSomeInt!T) { /* serialise integer */ } void serialise(void[] buffer, const(char)[] str) { /* serialise string */ } // ...etc ``` And some serialiser somewhere calls `serialise(thing)` for each thing, where the type of thing chooses the right overload and we're all good... sure, everyone knows this pattern. So, I add a user thing in a module somewhere: ``` module app.user_thing: struct UserThing { ... } void serialise(void[] buffer, ref UserThing t) { /* serialise UserThing */ } ``` Now this thing wants to be serialisable, so you implement a serialise function beside it... In C++, this works; because ADL (argument dependent lookup) will cause to additionally search the scope where the argument is defined for overloads. Trouble is, in D unless `app.user_thing` was imported inside the serialiser where it makes the call to `serialise()`, this overload won't be found, because the UserThing overload is not in scope for the serialiser. I tried to simulate something like ADL by getting `__traits(parent, value)` in a loop until I find the module for non-builtin objects, and then have the serialiser import that module prior to the call, to attempt to make sure the argument's module is also in scope so any potential overloads can be found when it tries to make the call: import default_serialise : serialise; void doSerialise(Things...)(void[] buffer, Things things) { static foreach (thing; things) {{ static if (isUserType!thing) { enum thingMod = getModuleForThing!thing; mixin(import " ~ thingMod ~ ";"); } serialise(buffer thing); }} } The surprise is that if `thingMod` has a symbol `serialise`, it imports it at the inner scope, and it shadows the global overload set rather than complementing it... So, I guess the thing I'm stuck on is; given there are some imports at global scope, and it may have an overload set for some function; HOW can I import more items to that overload set? I tried this, but it doesn't work: import default_serialise : serialise; void doSerialise(Things...)(void[] buffer, Things things) { static foreach (thing; things) {{ static if (isUserType!thing) { enum thingMod = getModuleForThing!thing; mixin(import " ~ thingMod ~ ";"); import default_serialise : serialise; // re-import at inner scope, beside the other one } serialise(buffer thing); }} } Re-importing the globals into the same scope doesn't cause them to combine either; just the symbol from whichever import statement appears first is the winner... Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....
Dec 03
On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:Re-importing the globals into the same scope doesn't cause them to combine either; just the symbol from whichever import statement appears first is the winner... Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....You could merge overloads like this: ``` module a; struct A { } void serialise(void[], A) { import std.stdio; writeln("A.serialize"); } --- module b; struct B { } void serialise(void[], B) { import std.stdio; writeln("B.serialise"); } --- module default_serialise; void serialise(T)(void[], T t) { import std.stdio; writeln("T.serialise"); } --- module main; enum isUserType(T) = true; // change to whatever // this is needed because we still cannot express "local to static foreach". template serialiseOf(T) { static if (is(__traits(parent, T) == module) && isUserType!T) { alias mod = __traits(parent, T); alias serialiseOf = mod.serialise; } else { static import default_serialise; alias serialiseOf = default_serialise.serialise; } } // this is needed because D doesn't allow overloading local functions. template doSerialiseImpl(Things...) { static foreach(Thing; Things) alias doSerialiseImpl = serialiseOf!Thing; } void doSerialise(Things...)(void[] buffer, Things things) { static foreach (thing; things) doSerialiseImpl!Things(buffer, thing); } void main() { import a, b; doSerialise(null, A(), B(), 42); } ``` It never ceases to amaze me how difficult it still is to make such trivial things work.
Dec 03
On Tuesday, 3 December 2024 at 15:17:47 UTC, Max Samukha wrote:static import default_serialise; alias serialiseOf = default_serialise.serialise;The static import is a leftover from a previous iteration. You should not need it.
Dec 03
On 12/3/2024 7:17 AM, Max Samukha wrote:// this is needed because we still cannot express "local to static foreach".That's because static foreach will become fairly useless if it introduced a scope. (Same as for static if.) However, you can still add { } to make a scope: ``` { static foreach(...) { } } ``` Or: ``` static foreach (...) {{ }} ```
Dec 03
On 12/3/24 23:16, Walter Bright wrote:On 12/3/2024 7:17 AM, Max Samukha wrote:It does introduce a scope for the loop variables, there is just no way to add your own temporary variables to that scope. I had this implemented as the `__local` storage class, but I did not include it in the DIP proposal to avoid bikeshedding over syntax delaying the entire `static foreach` implementation: https://github.com/dlang/dmd/commit/bac3426f9881e4a591df229423475efc6c3e0918 static foreach(i;0..10){ __local enum tmp = i*i; // use tmp here } // no redefinition error There was also `__previous`, that allowed to access the `__local` variables from the previous iteration, allowing more imperative-style code: https://github.com/dlang/dmd/commit/6c2f4b9c41cef5895efd3c1161920a47ed0b6464 static foreach(i;0..100){ enum next_i = i+1; static if(i){ static assert(__previous.i == i-1); static assert(__previous.next_i == i); } } It's a few lines of code to make that work though, the storage class exists, it's just not accessible to user code. People have been using mixin templates to tuck away their loop-body-local variables ever since, due to the way scoping works for mixin templates (if you mix in the same mixin template multiple times unnamed into the same scope, the declarations in them do not conflict, but may become inaccessible from outside the mixin template instance.) This is what that comment was on.// this is needed because we still cannot express "local to static foreach".That's because static foreach will become fairly useless if it introduced a scope. (Same as for static if.) However, you can still add { } to make a scope: ``` { static foreach(...) { } } ``` Or: ``` static foreach (...) {{ }} ```
Dec 03
On 04/12/2024 3:32 PM, Timon Gehr wrote:I had this implemented as the |__local| storage class, but I did not include it in the DIP proposal to avoid bikeshedding over syntax delaying the entire |static foreach| implementation:I'm trying to come up with alternative syntax. Everything so far, a scope guard, an attribute, everything is coming up as having worse trade offs. It may not feel right, but it has pretty good trade offs I think. But yeah I'd quite like having this.
Dec 03
On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....I gave this go using an eponymous template - it's definitely a bit yucky, but seems to do as you want (would obviously need more extensive work for real use cases rather than this simple POC): ```d module serialise; import std.traits : isNumeric; string serialise(T)(T foo) if(isNumeric!T) { import std.conv : to; return foo.to!string(); } string serialise(string s) { return s; } string doSerialise(Things...)(Things things) { string slowBuilder; static foreach (i, Thing; Things) {{ enum hasSerialiser = __traits(compiles, serialiserFor!Thing); static if(hasSerialiser) slowBuilder ~= serialiserFor!Thing(things[i]); else slowBuilder ~= serialise(things[i]); }} return slowBuilder; } template serialiserFor(Thing) { import std.traits : fullyQualifiedName; mixin("import thingMod = "~fullyQualifiedName!(__traits(parent, Thing))~";"); alias serialiserFor = thingMod.serialise; } ``` ```d module app; import std.stdio; void main() { import serialise; import std : writeln; writeln(doSerialise(UserThing("a"))); writeln(doSerialise("a")); } struct UserThing { string a; } string serialise(UserThing thing) { return "UserThing: " ~ thing.a; } ``` ``` UserThing: a a ``` Namely: The use of an eponymous template helps avoid the symbol resolution issue - you could also try to mixin a local import like `import userthing : userThingSerialise = serialise` instead, but a template might be cleaner. I use a `__traits(compiles)` ~~abuse~~ check since it's simple, but you can probably also do some stuff with [__traits(hasMember)](https://dlang.org/spec/traits.html#hasMember), `__traits(getMember)`, etc. Another potential way if keeping the serialise function separate from the actual type is mandatory, which I can't be bothered to prototype, is to make a template struct similar to this: ```d struct Serialiser(UserThingT, alias SerialiserFunc){...} ``` And then have a specific overload within the main serialiser module to handle this case: ```d // Something like this at least. yada serialise(Thing)(...) if(isInstanceOf!(Serialiser, Thing)) { // Use SerialiserFunc against Thing? } ``` Though there's probably a bunch of issues with lifetimes, const, ref, etc. with that approach. Definitely an interesting issue, though personally I'd try my best to allow types to have a `serialise` function directly be a part of them rather than using free standing extentions.
Dec 03
On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:import default_serialise : serialise; void doSerialise(Things...)(void[] buffer, Things things) { static foreach (thing; things) {{ static if (isUserType!thing) { enum thingMod = getModuleForThing!thing; mixin(import " ~ thingMod ~ ";"); } serialise(buffer thing); }} }BTW, do you really need to create the overload set? You could call the right `serialize` directly if one exists in the type's module and fall back to the default one if it doesn't.
Dec 03
This won't help you today but: 1. We are considering giving structs inheritance as part of replacing alias this. 2. I want a way to mark a method as 'reinterpreted' by the child class/struct. Specifically for serialization, removing the need for doing any lookups like this.
Dec 03
On Tuesday, 3 December 2024 at 17:35:36 UTC, Richard (Rikki) Andrew Cattermole wrote:This won't help you today but: 1. We are considering giving structs inheritance as part of replacing alias this. 2. I want a way to mark a method as 'reinterpreted' by the child class/struct. Specifically for serialization, removing the need for doing any lookups like this.Please no. Than then also makes the betterC subset too complex. What facility of 'alias this' is it intended to preserve? If only the implicit conversion, then a new form of operator, or special named member function strikes me as more suitable. From my perspective, one the the nice things about the class / strict difference is that struct does not have any classful behaviour, i.e. it has no inheritance. If you add inheritance, that adds a thinking burden when reading code, and essentially removes the difference between class and struct, other than the implicit lock within classes. I view the 'reference type' thing as a rather trivial difference. One could add something like the struct embedding of Go/Limbo/Alef/Ken-C together with its implicit conversion for methods/functions. The method is then interpreted by the embedded element, but against the embedded element, not the parent one.
Dec 03
On 04/12/2024 7:22 AM, Derek Fawcus wrote:On Tuesday, 3 December 2024 at 17:35:36 UTC, Richard (Rikki) Andrew Cattermole wrote:My ideas post: https://forum.dlang.org/post/llqcjziyurwmyhzseonm forum.dlang.org The problem is alias this is too complex, and has introduced bugs that cannot be fixed, as it is relied upon. Struct inheritance alone cannot fix it (note I did not originally propose this, that was Walter), my proposal is to add a way to reparent the parent most type, to replicate a lot of use cases of alias this without the bad behavior. As a follow user of -betterC, I want to get full D classes working in it. Because there is absolutely no reason why they should not work (although such things as Object root class would not be present, therefore would need to be explicit). This may seem unnecessary, but removing the coupling between the language implementation and druntime is important for pay as you go users, and with that custom runtimes. Which offers us greater portability.This won't help you today but: 1. We are considering giving structs inheritance as part of replacing alias this. 2. I want a way to mark a method as 'reinterpreted' by the child class/struct. Specifically for serialization, removing the need for doing any lookups like this.Please no. Than then also makes the betterC subset too complex. What facility of 'alias this' is it intended to preserve? If only the implicit conversion, then a new form of operator, or special named member function strikes me as more suitable. From my perspective, one the the nice things about the class / strict difference is that struct does not have any classful behaviour, i.e. it has no inheritance. If you add inheritance, that adds a thinking burden when reading code, and essentially removes the difference between class and struct, other than the implicit lock within classes. I view the 'reference type' thing as a rather trivial difference. One could add something like the struct embedding of Go/Limbo/Alef/Ken-C together with its implicit conversion for methods/functions. The method is then interpreted by the embedded element, but against the embedded element, not the parent one.
Dec 03
On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....as a compromise you could have an uda that packs custom serialisation defined on type itself or field of that type, then you can make serializer employ it when it sees it somewhere. Best regards, Alexandru.
Dec 03
On Tuesday, 3 December 2024 at 19:34:21 UTC, Alexandru Ermicioi wrote:On Tuesday, 3 December 2024 at 11:55:50 UTC, Manu wrote:That is essentially what Go does for its serialisation (Marshal and Unmarshal) routines. Only the public fields of a struct can be processed, and how to handle a field in a non default fashion is marked by an attribute - an extra string. ```Go type DelSite struct { SiteID types.SiteID `json:"site-id" validate:"min=1"` } ``` One of those is then encoded as follows, where 's' happens to be a struct ptr: ```Go var b []byte b, err := json.Marshal(s) ``` It is using run time reflection, but I see no obvious reason why compile time reflection could not be used instead, since that is what D has.Has anyone ever experimented with a pattern like this? Essentially, I can't work out how to expand /combine an overload set to include symbols from multiple imports....as a compromise you could have an uda that packs custom serialisation defined on type itself or field of that type, then you can make serializer employ it when it sees it somewhere.
Dec 05
On 06/12/2024 8:07 AM, Derek Fawcus wrote:It is using run time reflection, but I see no obvious reason why compile time reflection could not be used instead, since that is what D has.I said this over in ideas, but the problem isn't controlling serialization, but the entry point into it. Ideal: ```d class Parent { void serialize() { // An implementation based upon typeof(this) goes here, // currently this could only be Parent } } class Child : Parent { } ``` How it has to be implemented today to work: ```d class Parent { abstract void serialize(); protected void serialize_(T:Parent)(T self) { } } class Child : Parent { void serialize() { super.serialize_(this); } } ``` If you are forcing users of a parent class to add the call explicitly into the parent, its a major pain that other languages simply do not have. Not to mention easily forgotten. This is what my attribute that I said I'm thinking about `` reinterptAsChild`` would solve.
Dec 05
On Thursday, 5 December 2024 at 21:58:14 UTC, Richard (Rikki) Andrew Cattermole wrote:If you are forcing users of a parent class to add the call explicitly into the parent, its a major pain that other languages simply do not have. Not to mention easily forgotten. This is what my attribute that I said I'm thinking about `` reinterptAsChild`` would solve.I'm confused for example. What is the problem you're mentioning here? If it is inheritance, then you either predefine the inheritance structure through annotations just like Jakson lib in java https://www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/latest/com/fasterxml/jackson/annotatio /JsonTypeInfo.html, or use runtime reflection to check what child it is and use respective serializer for it that is prerecorded.
Dec 05
On 06/12/2024 1:47 PM, Alexandru Ermicioi wrote:On Thursday, 5 December 2024 at 21:58:14 UTC, Richard (Rikki) Andrew Cattermole wrote:We do not have runtime reflection. We cannot copy languages that do, including for your example that isn't an alternative to runtime reflection. ```d interface ISerialize { void serialize(); } class SerializableRoot { void serialize() { // serialize here } } class MyType : SerializableRoot { thing int field; ... } ``` Some how SerializableRoot.serialize has to be able to see the child MyType class. We have no good way of handling that currently. The attribute allows the serialize method to think that its in MyType instead.If you are forcing users of a parent class to add the call explicitly into the parent, its a major pain that other languages simply do not have. Not to mention easily forgotten. This is what my attribute that I said I'm thinking about `` reinterptAsChild`` would solve.I'm confused for example. What is the problem you're mentioning here? If it is inheritance, then you either predefine the inheritance structure through annotations just like Jakson lib in java https:// www.javadoc.io/doc/com.fasterxml.jackson.core/jackson-annotations/ latest/com/fasterxml/jackson/annotation/JsonTypeInfo.html, or use runtime reflection to check what child it is and use respective serializer for it that is prerecorded.
Dec 05
On Friday, 6 December 2024 at 01:18:03 UTC, Richard (Rikki) Andrew Cattermole wrote:Some how SerializableRoot.serialize has to be able to see the child MyType class. We have no good way of handling that currently.There is the curious recurring template pattern of course.
Dec 05
On 6/12/24 2:18, Richard (Rikki) Andrew Cattermole wrote:We do not have runtime reflection. We cannot copy languages that do, including for your example that isn't an alternative to runtime reflection. ```d interface ISerialize { void serialize(); } class SerializableRoot { void serialize() { // serialize here } } class MyType : SerializableRoot { thing int field; ... } ``` Some how SerializableRoot.serialize has to be able to see the child MyType class. We have no good way of handling that currently.```d import std; interface ISerialize { void serialize(); } struct thing{ } class SerializableRoot { void serialize(this T)() { // serialize here, possibly using __traits to introspect on T writeln("Serializing ", typeid(T)); } } class MyType : SerializableRoot { thing int field; } void main() { MyType c = new MyType; c.serialize; } ```
Dec 05
On 06/12/2024 8:59 PM, Arafel wrote:On 6/12/24 2:18, Richard (Rikki) Andrew Cattermole wrote:I'm fully aware of template this parameters, I was trying to get them to work 10 years ago. They have problems that prevent their usage. 1. They are templates, so not in vtable 2. They require the this pointer to be typed to the child (extremely easy for this to not be the case, see 1) 3. Because they are not virtual, you cannot up cast (see 2) 4. Cannot be over ridden for any manual behavior All and all, they become very complex for common usage, quite fragile if you don't use them right, not the kind of thing you recommend to people who are use to runtime reflection.We do not have runtime reflection. We cannot copy languages that do, including for your example that isn't an alternative to runtime reflection. ```d interface ISerialize { void serialize(); } class SerializableRoot { void serialize() { // serialize here } } class MyType : SerializableRoot { thing int field; ... } ``` Some how SerializableRoot.serialize has to be able to see the child MyType class. We have no good way of handling that currently.```d import std; interface ISerialize { void serialize(); } struct thing{ } class SerializableRoot { void serialize(this T)() { // serialize here, possibly using __traits to introspect on T writeln("Serializing ", typeid(T)); } } class MyType : SerializableRoot { thing int field; } void main() { MyType c = new MyType; c.serialize; } ```
Dec 06
On 6/12/24 9:05, Richard (Rikki) Andrew Cattermole wrote:I'm fully aware of template this parameters, I was trying to get them to work 10 years ago. They have problems that prevent their usage. 1. They are templates, so not in vtable 2. They require the this pointer to be typed to the child (extremely easy for this to not be the case, see 1) 3. Because they are not virtual, you cannot up cast (see 2) 4. Cannot be over ridden for any manual behavior All and all, they become very complex for common usage, quite fragile if you don't use them right, not the kind of thing you recommend to people who are use to runtime reflection.That's true, I found a workaround using constructors and registering classes there, but currently it only works with one level of inheritance (without user intervention, the whole purpose of it): ```d import std; class SerializationRoot { static void function()[TypeInfo_Class] serializationMethods; // We would need a `shared static` constructor that would be always called /* shared static */ this(this T)() { auto myType = typeid(T); if (myType !in serializationMethods) { writeln("Registering type ", myType); serializationMethods[myType] = (&T.serializeDefaultImpl!T).funcptr; } } void serialize() { auto myType = typeid(this); assert(myType in serializationMethods, "The class was not registered for default serialization."); void delegate() dlg; dlg.funcptr = serializationMethods[myType]; dlg.ptr = cast (void *) this; dlg(); stdout.flush; } void serializeDefaultImpl(T)() { T realThis = cast(T) this; writeln("Default serialization for ", typeid(realThis)); } } class MyClass : SerializationRoot { } class MyOtherClass : SerializationRoot { this(int) { } override void serialize() { writeln("This class does something special, but still uses the default mechanism."); super.serialize; } } class MySpecialClass : SerializationRoot { override void serialize() { writeln("This class disregards everything takes everything into its own hands."); } } class MyProblematicClass : MyClass { } void main() { SerializationRoot myClass = new MyClass; SerializationRoot myOtherClass = new MyOtherClass(1); SerializationRoot mySpecialClass = new MySpecialClass(); SerializationRoot myProblematicClass = new MyProblematicClass(); myClass.serialize; myOtherClass.serialize; mySpecialClass.serialize; myProblematicClass.serialize; // BOOM! } ``` But it should work as expect if we had templated-this (shared) static constructors. My reading is that they are actually not forbidden in the spec ("member functions" should include static ones too) [1], and there is already an open bug for that [2] (with some duplicates). Unfortunately I don't think it'll happen anytime soon, and I don't have the skills to have a go at it myself. [1]: https://dlang.org/spec/template.html#template_this_parameter [2]: https://issues.dlang.org/show_bug.cgi?id=10488
Dec 06
On 06/12/2024 10:46 PM, Arafel wrote:On 6/12/24 9:05, Richard (Rikki) Andrew Cattermole wrote:Yes, once you have a level in the hierarchy that adds a constructor, you basically lose the ability to call that constructor with the template this parameter. Which means the user derived type has to be final. A struct starts to look inviting with all these issues present in classes lolI'm fully aware of template this parameters, I was trying to get them to work 10 years ago. They have problems that prevent their usage. 1. They are templates, so not in vtable 2. They require the this pointer to be typed to the child (extremely easy for this to not be the case, see 1) 3. Because they are not virtual, you cannot up cast (see 2) 4. Cannot be over ridden for any manual behavior All and all, they become very complex for common usage, quite fragile if you don't use them right, not the kind of thing you recommend to people who are use to runtime reflection.That's true, I found a workaround using constructors and registering classes there, but currently it only works with one level of inheritance (without user intervention, the whole purpose of it):
Dec 06
On 6/12/24 11:18, Richard (Rikki) Andrew Cattermole wrote:Yes, once you have a level in the hierarchy that adds a constructor, you basically lose the ability to call that constructor with the template this parameter.Shared static constructors with a template this parameter would be the solution (just promoting them in case somebody feels like implementing them ;-) )
Dec 06
On 06/12/2024 11:38 PM, Arafel wrote:On 6/12/24 11:18, Richard (Rikki) Andrew Cattermole wrote:For deserialization sure. For serialization they cannot be overridden. In both cases you have to use a single global registration (which may not be a down side). So another case of it works for somethings, but not others.Yes, once you have a level in the hierarchy that adds a constructor, you basically lose the ability to call that constructor with the template this parameter.Shared static constructors with a template this parameter would be the solution (just promoting them in case somebody feels like implementing them ;-) )
Dec 06
Koenig lookup? I ran away, far away, from that, after implementing it for C++. It's a nightmare.
Dec 03
On 04/12/2024 11:12 AM, Walter Bright wrote:Koenig lookup? I ran away, far away, from that, after implementing it for C++. It's a nightmare.I got a bit of a chuckle out of that. You've been very careful with symbol lookup, its why I was pretty careful when designing my proposal for replacing alias this. Keep things nice and simple, by following the inheritance hierarchy only!
Dec 03
On 12/3/24 23:12, Walter Bright wrote:Koenig lookup? I ran away, far away, from that, after implementing it for C++. It's a nightmare.Sure, but the question was, how do you do actually do the serialization via introspection.
Dec 03
On 12/3/2024 6:39 PM, Timon Gehr wrote:Sure, but the question was, how do you do actually do the serialization via introspection.As I haven't attempted that, I might suggest use of: 1. interfaces 2. output ranges (sinks)
Dec 03
On 04/12/2024 8:33 PM, Walter Bright wrote:On 12/3/2024 6:39 PM, Timon Gehr wrote:Don't forget template this parameter! (its horrible)Sure, but the question was, how do you do actually do the serialization via introspection.As I haven't attempted that, I might suggest use of: 1. interfaces 2. output ranges (sinks)
Dec 03
On Wednesday, 4 December 2024 at 02:39:12 UTC, Timon Gehr wrote:Sure, but the question was, how do you do actually do the serialization via introspection.[ISerializable](https://learn.microsoft.com/en-us/dotnet/api/system.runtime.serializ tion.iserializable) interface. There's also aspect-oriented approach, but it's deprecated in favor of DTO pattern.
Dec 04
``` void serialize(byte[] o, string s){} struct MyDto { struct SerializerAspect { static void serialize(byte[] o, MyDto s){} } } void serialize2(T)(byte[] o, T s) if(!is(T.SerializerAspect)) { serialize(o,s); } void serialize2(T)(byte[] o, T s) if(is(T.SerializerAspect)) { T.SerializerAspect.serialize(o,s); } void test() { byte[] o; string a; serialize2(o,a); MyDto c; serialize2(o,c); } ```
Dec 04
Back before I ended work on C++, a number of C++ Illuminati told me privately that ADL was a mistake. Things may have changed since then, and I don't know about that.
Dec 03
On 04/12/2024 8:29 PM, Walter Bright wrote:Back before I ended work on C++, a number of C++ Illuminati told me privately that ADL was a mistake. Things may have changed since then, and I don't know about that.https://en.cppreference.com/w/cpp/language/adl Defects: CWG 33 C++98 CWG 90 C++98 CWG 239 C++98 CWG 997 C++98 CWG 1690 C++98 C++11 CWG 1691 C++11 CWG 1692 C++98 CWG 2857 C++98 That list sure gives confidence!
Dec 03
On Wed, 4 Dec 2024 at 17:31, Walter Bright via Digitalmars-d < digitalmars-d puremagic.com> wrote:Back before I ended work on C++, a number of C++ Illuminati told me privately that ADL was a mistake. Things may have changed since then, and I don't know about that.I've used it effectively for decades and never had any problems. I'm not advocating ADL necessarily, I can see the complexity; but we need an effective response though. D has made the patterns it supports somewhat more attractive than they are in C++ (mainly thanks to UFCS), yet the local imports shadowing overload sets issue is confusing and unintuitive, and makes the situation fail completely. For my money, SFINAE is the most egregious bit of the C++ spec. (though we're not doing a whole lot better!)
Dec 04
On Wed, 4 Dec 2024 at 08:16, Walter Bright via Digitalmars-d < digitalmars-d puremagic.com> wrote:Koenig lookup? I ran away, far away, from that, after implementing it for C++. It's a nightmare.So what do you suggest? UFCS makes this pattern more attractive than ever, but our lookup rules inhibit any practical application. The problem is essentially that a more-local import or singular declaration shadows an entire overload set from a less-local scope, rather than merging with it.
Dec 04
On Wednesday, December 4, 2024 7:31:50 PM MST Manu via Digitalmars-d wrote:On Wed, 4 Dec 2024 at 08:16, Walter Bright via Digitalmars-d < digitalmars-d puremagic.com> wrote:The way that I've seen serialization done in D is to use UDAs, and the (de)serializer uses type introspection to look at the UDAs to see what to do. It could handle certain types by default (e.g. the built in language types) and then extrapolate what to do for a type that is made up of known types but isn't marked up with UDAs - or it could require that any types be marked up to be (de)serialized. Either way, when dealing with a type that doesn't have a UDA, the variable declaration using that type could use a UDA to provide the required functions - or it could provide an intermediate type (be it one that has a serialize and deseralize function that handles the variable's type or because it's a type that that variable can be converted to and from that the (de)serializer knows how to handle). IIRC, the seralization stuff I saw in Java years ago did something similar (though obviously with runtime reflection). Though obviously, C++ isn't going to use that approach unless they've added some form of UDAs in a recent version, and I missed it. Obviously, that's a very different approach than what you're talking about, and it may or may not fit what you're ultimately looking to do, but it's what I've seen done - e.g. Mir has a serialization solution along those lines. - Jonathan M DavisKoenig lookup? I ran away, far away, from that, after implementing it for C++. It's a nightmare.So what do you suggest? UFCS makes this pattern more attractive than ever, but our lookup rules inhibit any practical application. The problem is essentially that a more-local import or singular declaration shadows an entire overload set from a less-local scope, rather than merging with it.
Dec 05
On Thursday, 5 December 2024 at 13:16:06 UTC, Jonathan M Davis wrote:IIRC, the seralization stuff I saw in Java years ago did something similar (though obviously with runtime reflection). Though obviously, C++ isn't going to use that approach unless they've added some form of UDAs in a recent version, and I missed it.Java serialisation libs still do employ annotations(UDA) for customising serialised output, and imho is much, much better than C++ approach.
Dec 05
On 12/3/24 12:55, Manu wrote:Maybe someone has a pattern for doing this kind of thing...There are a couple solutions that look okay already, but basically, importing into any unordered scope works, e.g. ```d module default_serialise; ubyte[] serialise(T)(T arg)if(!is(T==struct)){ return [1]; } ``` ```d module user_code; struct S{} ubyte[] serialise(S){ return [2]; } ``` ```d import default_serialise; void main(){ import std; static struct Dummy{ import default_serialise: serialise; import user_code: serialise; } import user_code; writeln(Dummy.serialise(1)); // [1] writeln(Dummy.serialise(S())); // [2] } ``` You can also use a dummy template scope (template Dummy(), and then `Dummy!().serialise`). I dislike function-local import semantics quite a bit as they do not follow the well-thought-out overloading rules that apply to other imports.
Dec 03
On Wed, 4 Dec 2024 at 12:56, Timon Gehr via Digitalmars-d < digitalmars-d puremagic.com> wrote:On 12/3/24 12:55, Manu wrote:"Unordered scope"; it never occurred to me that was a concept! But I can see that now... What a horrible hack, but I guess that could work. I'll see if I can do anything with this idea...Maybe someone has a pattern for doing this kind of thing...There are a couple solutions that look okay already, but basically, importing into any unordered scope works, e.g. ```d module default_serialise; ubyte[] serialise(T)(T arg)if(!is(T==struct)){ return [1]; } ``` ```d module user_code; struct S{} ubyte[] serialise(S){ return [2]; } ``` ```d import default_serialise; void main(){ import std; static struct Dummy{ import default_serialise: serialise; import user_code: serialise; } import user_code; writeln(Dummy.serialise(1)); // [1] writeln(Dummy.serialise(S())); // [2] } ``` You can also use a dummy template scope (template Dummy(), and then `Dummy!().serialise`). I dislike function-local import semantics quite a bit as they do not follow the well-thought-out overloading rules that apply to other imports.
Dec 04
On 12/3/2024 6:51 PM, Timon Gehr wrote:I dislike function-local import semantics quite a bit as they do not follow the well-thought-out overloading rules that apply to other imports.I don't know if anyone remembers, but there was a huge debate about this in the early days. I had implemented local import rules just like all the other rules, but everyone else deemed that unintuitive, and so we now have a difficult to explain mechanism that is intuitive.
Dec 11
On 12/12/24 07:07, Walter Bright wrote:On 12/3/2024 6:51 PM, Timon Gehr wrote:I don't think I was around for that, but to be fair, it is unlikely that we are on exactly the same page here. There are multiple ways to do this that avoid hijacking problems and you have vehemently argued against overloading at function scope in the past.I dislike function-local import semantics quite a bit as they do not follow the well-thought-out overloading rules that apply to other imports.I don't know if anyone remembers, but there was a huge debate about this in the early days. I had implemented local import rules just like all the other rules, but everyone else deemed that unintuitive, and so we now have a difficult to explain mechanism that is intuitive.
Dec 12