Jump to page: 1 2
Thread overview
Implicit Constructors
Oct 12, 2017
Q. Schroll
Oct 13, 2017
Jacob Carlborg
Oct 13, 2017
Adam D. Ruppe
Oct 13, 2017
Meta
Oct 13, 2017
Adam D. Ruppe
Oct 13, 2017
jmh530
Oct 13, 2017
Adam D. Ruppe
Oct 14, 2017
Q. Schroll
Oct 13, 2017
Meta
Oct 13, 2017
rikki cattermole
Oct 13, 2017
rikki cattermole
Oct 14, 2017
Q. Schroll
October 12, 2017
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
October 13, 2017
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
October 13, 2017
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
October 13, 2017
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
October 13, 2017
Lets just kill it.

It's an ugly unexpected piece of syntax.
October 13, 2017
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
October 13, 2017
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.
October 13, 2017
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!

October 13, 2017
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
October 13, 2017
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
}
« First   ‹ Prev
1 2