www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Implicit Constructors

reply Q. Schroll <qs.il.paperinik gmail.com> writes:
We have some sort of implicit construction already. Weirdly, it's 
reserved for classes. Just look at this:

     class C { this(int x) { } }
     void foo(C c ...) { }
     void main() { foo(0); }

If you put  nogc in front of ctor and functions, the compiler 
tells you not to use 'new' in main while you actually don't. 
Merely the compiler inserts it for you to complain about it.

One could propose to extend the three-dots notation to structs. I 
don't. I'd vote for deprecating the three-dots for classes. Did 
you know it exists? Did you use it - like ever? Does anyone 
depend on it?

(If you don't want to read it all: The examples may be expressing 
enough.)

The main point of this post is a library solution to implicit 
constructor calls. The implementation is very conservative: A 
double handshake; not the constructors must be annotated with 
 implicit, the functions which want to allow being called with a 
constructor parameter must explicitly state that (these functions 
are called "receiving" functions).  implicit constructors must 
have exactly one parameter (no defaulted additional ones) and a 
receiving function has an annotation  implicit(i) where i is the 
index of a parameter for which it will be allowed to plug in a 
constructor argument of its type. Sounds complicated? See an 
example.

     struct S
     {
         import bolpat.implicitCtor : implicit;
         long s;
          implicit this(int x)  { s = x; }
          implicit this(long x) { s = x; }
         this(bool x) { s = x ? 0 : -1; }
     }

This is all that you need from the one side. Now the receiver 
side.

     import bolpat.implicitCtor : implicit, implicitOverloads;

     long proto_goo(int v, S s, bool b)  implicit(1)
     {
         import std.stdio : writeln;
         writeln("goo: call S with value ", s.s);
         return b ? v : s.s;
     }
     void proto_goo(char c) { } // no  implicit(i) ==> will be 
ignored

     mixin implicitOverloads!("goo", proto_goo); // generates goo

     assert(goo(1, 2, false) == 2);

It also works for members. See:

     struct Test
     {
         int proto_foo(int v, S s)  implicit(1)
         {
             import std.stdio : writeln;
             writeln("foo: call S with value ", s.s);
             return v;
         }

         void proto_foo(char c) { } // ignored

         mixin implicitOverloads!("foo", proto_foo);
     }

What to do further? Make  implicit take more than one argument. 
I'm working on it. This is just a first taste. And for Stefan 
Koch, thanks to static foreach, one can safe so many templates.

tl;dr the implementation is here:
https://github.com/Bolpat/dUtility/blob/master/bolpat/implicitCtor.d
Oct 12
next sibling parent reply Jacob Carlborg <doob me.com> writes:
On 2017-10-13 01:57, Q. Schroll wrote:
 We have some sort of implicit construction already. Weirdly, it's 
 reserved for classes. Just look at this:
 
      class C { this(int x) { } }
      void foo(C c ...) { }
      void main() { foo(0); }
Hmm, I didn't know that syntax was legal. But apparently it's some form of typesafe variadic function [1]. It also work for built-in types: void foo(int i ...){} void main() { foo(3); foo(3, 4); // error, too many arguments } Not sure what the purpose of the latter is. [1] https://dlang.org/spec/function.html#typesafe_variadic_functions -- /Jacob Carlborg
Oct 12
next sibling parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/13/17 2:33 AM, Jacob Carlborg wrote:
 Hmm, I didn't know that syntax was legal. But apparently it's some form 
 of typesafe variadic function [1]. It also work for built-in types:
 
 void foo(int i ...){}
 
 void main()
 {
      foo(3);
      foo(3, 4); // error, too many arguments
 }
 
 Not sure what the purpose of the latter is.
 
 [1] https://dlang.org/spec/function.html#typesafe_variadic_functions
 
I believe it probably calls the builtin constructor: auto i = int(3); auto i = int(3, 4); // error Indeed it seems useless to have such a thing, as most builtin ctors would be equivalent to passing a convertible value anyway. -Steve
Oct 13
prev sibling parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 13 October 2017 at 06:33:14 UTC, Jacob Carlborg wrote:
 Not sure what the purpose of the latter is.
I think it is just so you can do (T)(T...) in a template and have it work across more types; unified construction syntax. Though why it doesn't work with structs is beyond me.
Oct 13
parent reply Meta <jared771 gmail.com> writes:
On Friday, 13 October 2017 at 13:19:24 UTC, Adam D. Ruppe wrote:
 On Friday, 13 October 2017 at 06:33:14 UTC, Jacob Carlborg 
 wrote:
 Not sure what the purpose of the latter is.
I think it is just so you can do (T)(T...) in a template and have it work across more types; unified construction syntax. Though why it doesn't work with structs is beyond me.
It'd be nice if it did, because I believe it would enable the following: import std.stdio; import std.variant; void test(Variant[] va...) { foreach (v; va) { writeln(v.type); } } void main() { test(1, "asdf", false); //Currently doesn't compile }
Oct 13
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 13 October 2017 at 14:22:05 UTC, Meta wrote:
 It'd be nice if it did, because I believe it would enable the 
 following:
I don't think so, since the implicit construction would only work one level deep. So you can implicit construct the array, but not the individual variants in the array.
Oct 13
next sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/13/17 10:28 AM, Adam D. Ruppe wrote:
 On Friday, 13 October 2017 at 14:22:05 UTC, Meta wrote:
 It'd be nice if it did, because I believe it would enable the following:
I don't think so, since the implicit construction would only work one level deep. So you can implicit construct the array, but not the individual variants in the array.
I concur, it's easily testable: class C { this(int x) {} } void foo(C a ...) { } void bar(C[] a ...) { } void main() { foo(0); // works bar(0); // does not work. } However, this could be useful: foo(Variant v ...) { } foo(0); foo("hello"); ... -Steve
Oct 13
parent reply jmh530 <john.michael.hall gmail.com> writes:
On Friday, 13 October 2017 at 14:37:25 UTC, Steven Schveighoffer 
wrote:
 [snip]
 However, this could be useful:

 foo(Variant v ...)
 {
 }

 foo(0);
 foo("hello");
 ...

 -Steve
You should be able to that with Adam's jsvar module, no? The issue is with foo(0, "hello");
Oct 13
parent reply Adam D. Ruppe <destructionator gmail.com> writes:
On Friday, 13 October 2017 at 14:43:41 UTC, jmh530 wrote:
 You should be able to that with Adam's jsvar module, no? The 
 issue is with
No, the D language doesn't allow it at all with structs; even with mine, you need to `foo(var(0))` or similar (if you define the function, you could define as `foo(T...)(T t)` and wrap to vars internally). Of course, you could also do: var foo = &.foo; foo(0); lol letting it convert all through the dynamic type :P But actually, I really wish D just had implicit ctors on the types themselves. I think C++'s mistake was that implicit was the default, and you have to write `explicit`. If we did the opposite, where implicit was opt in, I think it would be useful without the worry C++ had.
Oct 13
parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 13 October 2017 at 14:50:44 UTC, Adam D. Ruppe wrote:
 [snip]
 But actually, I really wish D just had implicit ctors on the 
 types themselves. I think C++'s mistake was that implicit was 
 the default, and you have to write `explicit`. If we did the 
 opposite, where implicit was opt in, I think it would be useful 
 without the worry C++ had.
Not completely. Walter and Andrei oppose even explicitly annotated implicit constructors [1, 2]. With my solution, you state a two sided desire, like offer and acceptance. Contrary to (even explicitly annotated, non-default) implicit constructors being the only necessity for getting implicit constructor calls, this makes it very transparent what's happening. The only exception is when `S` is in a library, you use implicit(0) on it, and the supplier decides to add more implicit constructors to `S`. This is what we (will) have future [3] for. [1] https://issues.dlang.org/show_bug.cgi?id=4875#c5 [2] https://issues.dlang.org/show_bug.cgi?id=7019#c8 [3] https://github.com/dlang/DIPs/blob/master/DIPs/DIP1007.md
Oct 14
prev sibling parent Meta <jared771 gmail.com> writes:
On Friday, 13 October 2017 at 14:28:43 UTC, Adam D. Ruppe wrote:
 On Friday, 13 October 2017 at 14:22:05 UTC, Meta wrote:
 It'd be nice if it did, because I believe it would enable the 
 following:
I don't think so, since the implicit construction would only work one level deep. So you can implicit construct the array, but not the individual variants in the array.
You can get very close but I don't think this will work with inheritance. If that could be fixed then it should be a workable solution (I think): class VArray { Variant[] va; this(T...)(T ts) { foreach(t; ts) { va ~= Variant(t); } } } void test2(VArray ta...) { foreach (v; ta.va) { writeln(v.type); } } void main() { test2(1, "asdf", false); }
Oct 13
prev sibling parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/12/17 7:57 PM, Q. Schroll wrote:
 We have some sort of implicit construction already. Weirdly, it's 
 reserved for classes. Just look at this:
 
      class C { this(int x) { } }
      void foo(C c ...) { }
      void main() { foo(0); }
 
 If you put  nogc in front of ctor and functions, the compiler tells you 
 not to use 'new' in main while you actually don't. Merely the compiler 
 inserts it for you to complain about it.
Not sure where you put the nogc. What is likely happening is that the call to foo is lowered to foo(new C(0)). Indeed, using -vcg-ast proves it. The spec says it can put the class on the stack, but is not required to.
 
 One could propose to extend the three-dots notation to structs. I don't. 
The fact that this is not supported (it isn't, I tried it) doesn't make any sense. It's likely this hails from a time where classes had ctors and structs did not, and is just not a feature that anyone cared about or used. IMO, it should be extended for structs just in terms of consistency. But I don't think it would be a high priority.
 I'd vote for deprecating the three-dots for classes. Did you know it 
 exists? Did you use it - like ever? Does anyone depend on it?
I'm mixed on it. I wouldn't care personally if it was removed, but it's a feature that may be used somewhere, and there's no harm in keeping it.
 
 (If you don't want to read it all: The examples may be expressing enough.)
 
 The main point of this post is a library solution to implicit 
 constructor calls. The implementation is very conservative: A double 
 handshake; not the constructors must be annotated with  implicit, the 
 functions which want to allow being called with a constructor parameter 
 must explicitly state that (these functions are called "receiving" 
 functions).  implicit constructors must have exactly one parameter (no 
 defaulted additional ones) and a receiving function has an annotation 
  implicit(i) where i is the index of a parameter for which it will be 
 allowed to plug in a constructor argument of its type. Sounds 
 complicated? See an example.
 
      struct S
      {
          import bolpat.implicitCtor : implicit;
          long s;
           implicit this(int x)  { s = x; }
           implicit this(long x) { s = x; }
          this(bool x) { s = x ? 0 : -1; }
      }
 
 This is all that you need from the one side. Now the receiver side.
 
      import bolpat.implicitCtor : implicit, implicitOverloads;
 
      long proto_goo(int v, S s, bool b)  implicit(1)
      {
          import std.stdio : writeln;
          writeln("goo: call S with value ", s.s);
          return b ? v : s.s;
      }
      void proto_goo(char c) { } // no  implicit(i) ==> will be ignored
 
      mixin implicitOverloads!("goo", proto_goo); // generates goo
 
      assert(goo(1, 2, false) == 2);
 
 It also works for members. See:
 
      struct Test
      {
          int proto_foo(int v, S s)  implicit(1)
          {
              import std.stdio : writeln;
              writeln("foo: call S with value ", s.s);
              return v;
          }
 
          void proto_foo(char c) { } // ignored
 
          mixin implicitOverloads!("foo", proto_foo);
      }
 
 What to do further? Make  implicit take more than one argument. I'm 
 working on it. This is just a first taste. And for Stefan Koch, thanks 
 to static foreach, one can safe so many templates.
 
 tl;dr the implementation is here:
 https://github.com/Bolpat/dUtility/blob/master/bolpat/implicitCtor.d
It's a neat idea. I don't see why we would need to remove the typesafe variadics to allow this to work. It *really* would be nice though, to allow annotations on parameters. The implicit(1) stinks. Would look much better as: proto_goo(int v, implicit S s, bool b); Where you may run into trouble is if there is ambiguity (for instance 2 implicit parameters could match the potential arguments in different ways). Another option is to not worry about tagging which parameters would be implicit, and go only on the fact that types in the parameter list have implicit constructors when you call implicitOverloads. -Steve
Oct 13
next sibling parent reply rikki cattermole <rikki cattermole.co.nz> writes:
Lets just kill it.

It's an ugly unexpected piece of syntax.
Oct 13
parent reply Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/13/17 9:04 AM, rikki cattermole wrote:
 Lets just kill it.
 
 It's an ugly unexpected piece of syntax.
It may be used somewhere, and then what is the migration path for those people? I don't see that it's harming anything having it there, most of us didn't even know about it. It's also not necessary to remove the feature in order to build a library that does similar things, and the syntax isn't needed elsewhere. It is bizarre, though, that it works only for classes and builtins, and not for structs. I have experienced with Swift the team killing "ugly" features, and it's painful. -Steve
Oct 13
parent reply rikki cattermole <rikki cattermole.co.nz> writes:
On 13/10/2017 2:07 PM, Steven Schveighoffer wrote:
 On 10/13/17 9:04 AM, rikki cattermole wrote:
 Lets just kill it.

 It's an ugly unexpected piece of syntax.
It may be used somewhere, and then what is the migration path for those people? I don't see that it's harming anything having it there, most of us didn't even know about it.
1) Warning, then actual removal. It'll still be available for a few releases for people to update their code 2) Fairly simple replacement: new Foo(0)
 It's also not necessary to remove the feature in order to build a 
 library that does similar things, and the syntax isn't needed elsewhere.
 
 It is bizarre, though, that it works only for classes and builtins, and 
 not for structs.
 
 I have experienced with Swift the team killing "ugly" features, and it's 
 painful.
And yet I expected the 0 there to be null. It would make a whole lot more sense then allocating a new instance which is considerably more expensive operation and not even used anywhere else!
Oct 13
parent Steven Schveighoffer <schveiguy yahoo.com> writes:
On 10/13/17 9:23 AM, rikki cattermole wrote:
 On 13/10/2017 2:07 PM, Steven Schveighoffer wrote:
 On 10/13/17 9:04 AM, rikki cattermole wrote:
 Lets just kill it.

 It's an ugly unexpected piece of syntax.
It may be used somewhere, and then what is the migration path for those people? I don't see that it's harming anything having it there, most of us didn't even know about it.
1) Warning, then actual removal. It'll still be available for a few releases for people to update their code 2) Fairly simple replacement: new Foo(0)
It's not that simple: void foo(alias x)() { x(0); // can't replace this with x(Foo(0)) } void bar(int x); void baz(Foo x ...); foo!bar(); foo!baz();
 
 
 It's also not necessary to remove the feature in order to build a 
 library that does similar things, and the syntax isn't needed elsewhere.

 It is bizarre, though, that it works only for classes and builtins, 
 and not for structs.

 I have experienced with Swift the team killing "ugly" features, and 
 it's painful.
And yet I expected the 0 there to be null. It would make a whole lot more sense then allocating a new instance which is considerably more expensive operation and not even used anywhere else!
0 does not implicitly cast to null in D. I would have expected a stack allocation of the object. That would be consistent with other typesafe variadics: void foo(int[] x ...); foo(1, 2, 3); // allocate array on the stack It even says in the spec that storing the class reference somewhere else is a bad idea because it *could* allocate on the stack. -Steve
Oct 13
prev sibling parent Q. Schroll <qs.il.paperinik gmail.com> writes:
On Friday, 13 October 2017 at 13:01:48 UTC, Steven Schveighoffer 
wrote:
 On 10/12/17 7:57 PM, Q. Schroll wrote:
 We have some sort of implicit construction already. Weirdly, 
 it's reserved for classes. Just look at this:
 
      class C { this(int x) { } }
      void foo(C c ...) { }
      void main() { foo(0); }
 
 If you put  nogc in front of ctor and functions, the compiler 
 tells you not to use 'new' in main while you actually don't. 
 Merely the compiler inserts it for you to complain about it.
Not sure where you put the nogc.
class C { this(int x) nogc { } } void foo(C c ...) nogc { } void main() nogc { foo(0); } It tells you not to use 'new' while you don't (explicitly, at least).
 What is likely happening is that the call to foo is lowered to 
 foo(new C(0)). Indeed, using -vcg-ast proves it.
Probably. I don't care -- the compiler should not give me this error message. I've filed a bug report, but I cannot find it anymore.
 The spec says it can put the class on the stack, but is not 
 required to.
Exactly. It shouldn't work and doesn't. That's not the problem.
 One could propose to extend the three-dots notation to 
 structs. I don't.
The fact that this is not supported (it isn't, I tried it) doesn't make any sense.
It tried once, too.
 It's likely this hails from a time where classes had ctors and 
 structs did not, and is just not a feature that anyone cared 
 about or used.

 IMO, it should be extended for structs just in terms of 
 consistency. But I don't think it would be a high priority.
That would be another consistent solution. Even if we had this for structs, there is the nogc argument not to allow it for classes (the compiler inserts nontrivial things: the heap allocation).
 I'd vote for deprecating the three-dots for classes. Did you 
 know it exists? Did you use it - like ever? Does anyone depend 
 on it?
I'm mixed on it. I wouldn't care personally if it was removed, but it's a feature that may be used somewhere, and there's no harm in keeping it.
Even extending this to structs does not give you implicit ctor calls. You can use ... only for the last parameter for obvious reasons. It's completely different from implicit ctor calls. I only mentioned that as it is the closest thing in D to implicit ctor calls.
 [snip]
It's a neat idea. I don't see why we would need to remove the typesafe variadics to allow this to work.
You don't. I mentioned it as it is somehow implicit ctor call.
 It *really* would be nice though, to allow annotations on 
 parameters. The  implicit(1) stinks. Would look much better as:

 proto_goo(int v,  implicit S s, bool b);
I tried that, too, and failed because of that. (I'd even assume anyone would, because it'd be the obvious way to want it.) This is another reason to allow that.
 Where you may run into trouble is if there is ambiguity (for 
 instance 2 implicit parameters could match the potential 
 arguments in different ways).
How? I only accept *one* parameter. Ctors with more than one parameter are disallowed. One could allow those which can be called with one parameter because they fill the rest with default values. I didn't for the sake of an easier implementation. It's a first sketch, a proof of concept.
 Another option is to not worry about tagging which parameters 
 would be implicit, and go only on the fact that types in the 
 parameter list have  implicit constructors when you call 
 implicitOverloads.
There are two reasons against it. 1. implicitOverloads would search much more for nothing. 2. You'd add implicit overloads the author of the function maybe wouldn't want. You can think of my system as offer and acceptance. You need both. implicit ctors do nothing for themselves the same way implicit(1) does nothing if the targeted type has nothing to offer. That's on purpose to make implicit ctor calls as transparent as possible. Walter didn't want implicit construction because it is non-transparent. Under these circumstances, it has good chances to be accepted for Phobos.
Oct 14