www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Post-ctor ctor

reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Just throwing an idea..

structs are great since you don't have to make a special constructor
just to initialize its fields. E.g.:

struct Foo
{
    int a;
    int b;
    int c;
    int d;
}

void main()
{
    auto foo = Foo(1, 2, 3, 4);
}

But what if I add an extra couple of fields that have to be
initialized based on only the first 4 fields, and I still want to keep
the same call in the user code? Here's a hypothetical case:

struct Foo
{
    int a;
    int b;
    int c;
    int d;

    int sum;
    int average;

    post this()  // post-ctor ctor
    {
        sum = a + b + c + d;
        average = (a + b + c + d) / 4;
    }
}

void main()
{
    auto foo = Foo(1, 2, 3, 4);
}

A post-construction ctor would do any final initializations after
field initialization, or after a call to a custom ctor. Otherwise I
would have to write:

struct Foo
{
    int a;
    int b;
    int c;
    int d;

    int sum;
    int average;

    this(int a, int b, int c, int d)
    {
        this.a = a;
        this.b = b;
        this.c = c;
        this.d = d;
        postCtor();
    }

    private void postCtor()
    {
        sum = a + b + c + d;
        average = (a + b + c + d) / 4;
    }
}

void main()
{
    auto foo = Foo(1, 2, 3, 4);
}

I see that as needlessly implementing in user-code what the compiler
can already do on its own. If we had a post-ctor ctor, partial
initialization could be done via field initialization instead of using
a hand-written ctor, and then a post-ctor would initialize the rest of
the fields.

Good idea / bad idea?

I have a feeling this would clash with that "can't have a default ctor
on a struct" rule. I just hate having to manually write ctors for
cases where the first N fields are initialized in order while the rest
need special initialization.
Aug 07 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Andrej Mitrovic:

 Good idea / bad idea?
I prefer a solution that to me looks simpler, discussed a bit here: http://d.puremagic.com/issues/show_bug.cgi?id=3878 With that idea your struct becomes: struct Foo { int a, b, c, d, sum, average; this(int this.a, int this.b, int this.c, int this.d) { sum = a + b + c + d; average = (a + b + c + d) / 4; } } Bye, bearophile
Aug 07 2011
next sibling parent bearophile <bearophileHUGS lycos.com> writes:
 With that idea your struct becomes:
Or just: struct Foo { int a, b, c, d, sum, average; this(this.a, this.b, this.c, this.d) { sum = a + b + c + d; average = (a + b + c + d) / 4; } } Bye, bearophile
Aug 07 2011
prev sibling parent reply Trass3r <un known.com> writes:
     this(int this.a, int this.b, int this.c, int this.d) {
         sum = a + b + c + d;
         average = (a + b + c + d) / 4;
     }
Can't express how awkward this is.
Aug 07 2011
parent bearophile <bearophileHUGS lycos.com> writes:
Trass3r:

     this(int this.a, int this.b, int this.c, int this.d) {
         sum = a + b + c + d;
         average = (a + b + c + d) / 4;
     }
Can't express how awkward this is.
Why don't you try to express how awkward it is? Well, code like this is shorter than what the OP has suggested, it's DRY, it's probably easy to understand (I think you don't need to go look into D docs every time you see something like this), it's easy to type, it avoids a common bug in my code (caused by mixing fields and arguments in the constructor): this(this.a, this.b, this.c, this.d) { sum = a + b + c + d; average = (a + b + c + d) / 4; } Bye, bearophile
Aug 07 2011
prev sibling parent reply Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/7/11 1:16 PM, Andrej Mitrovic wrote:
 Just throwing an idea..

 structs are great since you don't have to make a special constructor
 just to initialize its fields. E.g.:

 struct Foo
 {
      int a;
      int b;
      int c;
      int d;
 }

 void main()
 {
      auto foo = Foo(1, 2, 3, 4);
 }

 But what if I add an extra couple of fields that have to be
 initialized based on only the first 4 fields, and I still want to keep
 the same call in the user code?
Just define a mixin and use it: this(int a, int b, int c, int d) { mixin(initFromParameters()); ... } A similar macro could help assignments. Andrei
Aug 07 2011
next sibling parent reply bearophile <bearophileHUGS lycos.com> writes:
Andrei Alexandrescu:

 this(int a, int b, int c, int d) {
    mixin(initFromParameters());
    ...
 }
 
 A similar macro could help assignments.
Do you see people using that in real code? Bye, bearophile
Aug 08 2011
parent Andrei Alexandrescu <SeeWebsiteForEmail erdani.org> writes:
On 8/8/11 1:59 PM, bearophile wrote:
 Andrei Alexandrescu:

 this(int a, int b, int c, int d) {
     mixin(initFromParameters());
     ...
 }

 A similar macro could help assignments.
Do you see people using that in real code?
Yes. Andrei
Aug 08 2011
prev sibling parent reply David Nadlinger <see klickverbot.at> writes:
On 8/8/11 6:08 AM, Andrei Alexandrescu wrote:
 On 8/7/11 1:16 PM, Andrej Mitrovic wrote:
 struct Foo
 {
 int a;
 int b;
 int c;
 int d;
 }
Just define a mixin and use it: this(int a, int b, int c, int d) { mixin(initFromParameters()); ... }
There is currently no »legal« way to get the parameter names in D (other than parsing the .stringof output for the function type, which is not guaranteed to work, as far as I know), so I don't see how this could be implemented. Did I miss something obvious? David
Aug 08 2011
next sibling parent Timon Gehr <timon.gehr gmx.ch> writes:
David Nadlinger wrote:
 On 8/8/11 6:08 AM, Andrei Alexandrescu wrote:
 On 8/7/11 1:16 PM, Andrej Mitrovic wrote:
 struct Foo
 {
 int a;
 int b;
 int c;
 int d;
 }
Just define a mixin and use it: this(int a, int b, int c, int d) { mixin(initFromParameters()); ... }
There is currently no »legal« way to get the parameter names in D (other than parsing the .stringof output for the function type, which is not guaranteed to work, as far as I know), so I don't see how this could be implemented. Did I miss something obvious? David
Always use structs and classes with exactly 4 fields and always name them (as well as the constructor arguments) a,b,c,d.
Aug 08 2011
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
 On 8/8/11 6:08 AM, Andrei Alexandrescu wrote:
 On 8/7/11 1:16 PM, Andrej Mitrovic wrote:
 struct Foo
 {
 int a;
 int b;
 int c;
 int d;
 }
Just define a mixin and use it: this(int a, int b, int c, int d) { mixin(initFromParameters()); ... }
There is currently no »legal« way to get the parameter names in D (other than parsing the .stringof output for the function type, which is not guaranteed to work, as far as I know), so I don't see how this could be implemented. Did I miss something obvious?
In this particular example, what goes in initFromParameters is specific to Foo anyway. So, you'd have to write it by hand regardless. - Jonathan M Davis
Aug 08 2011
parent reply David Nadlinger <see klickverbot.at> writes:
On 8/8/11 10:41 PM, Jonathan M Davis wrote:
 In this particular example, what goes in initFromParameters is specific to Foo
 anyway. So, you'd have to write it by hand regardless.
If Andrei intended his answer this way, I don't quite see how it would be an appropriate solution, as Andrej explicitly stated in his original post: »I see that as needlessly implementing in user-code what the compiler can already do on its own.« David
Aug 08 2011
parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
 On 8/8/11 10:41 PM, Jonathan M Davis wrote:
 In this particular example, what goes in initFromParameters is specific
 to Foo anyway. So, you'd have to write it by hand regardless.
If Andrei intended his answer this way, I don't quite see how it would be an appropriate solution, as Andrej explicitly stated in his original post: »I see that as needlessly implementing in user-code what the compiler can already do on its own.«
Well, I don't see what the compiler could do on its own here. It'll generate the basic constructor which takes the struct's member variables in the order that they're declared, but it doesn't (and can't) generate the extra code that he wants which does other stuff to set those member variables, e.g. sum = a + b + c + d; That kind of code requires the programmer. If we had a post-constructor like Andrej is looking for, then he could put the code in there, but the code still has to go _somewhere_. The initialization of the first 4 member variables as happens in the example might be nice to do via traits so that the first part of the constructor could be generated (which would be what the compiler generates on its own), but the second part (the part which would go in the post-constructor) still has to be written by hand regardless. So, best case, initFromParameters could use traits to generate this.a = a; this.b = b; this.c = c; this.d = d; but the sum = a + b + c + d; average = (a + b + c + d) / 4; would have to be written by hand. Honestly, I think that using a mixin here would be overkill. You might as well just rewrite the whole thing in each constructor. The real gain of something like a post constructor is in not having to write the first portion of the constructor. In the case where you have multiple constructors which need a common set of initialization code but which can't call a common constructor, because there's no default constructor, you can just use a function for that. But if you want to essentially avoid writing half of the constructor be letting the compiler do it for you, you need something like a post constructor. Personally, I'd just write the constructor and be done with it, but I can see why Andrej would want something like this. If you want to do a mixin for this though, then you probably need to mix in the whole constructor rather than just the body like Andrei did. Then it can name the parameters itself, and you don't have the problem of having no way to query for the function parameters. It also avoids having to write the constructor's signature. However, unless you can write the mixin in a generic way which allows you to reuse it among structs (which would probably mean giving it a function to call which did the struct-specific stuff after the basic initalization), a mixin seems like total overkill. So, maybe you could do something like struct Foo { int a; int b; int c; int d; int sum; int average; mixin(structInit!(4, postConstructor)); private postConstructor() { sum = a + b + c + d; average = (a + b + c + d) / 4; } } and structInit would generate this(int a, int b, int c, int d) { this.a = a; this.b = b; this.c = c; this.d = d; initFunc(); } However, that sort of template seems very writeable, and it would be completely reusable, so it's probably a decent solution to the problem. - Jonathan M Davis
Aug 08 2011
parent reply David Nadlinger <see klickverbot.at> writes:
On 8/9/11 12:25 AM, Jonathan M Davis wrote:
 On 8/8/11 10:41 PM, Jonathan M Davis wrote:
 In this particular example, what goes in initFromParameters is specific
 to Foo anyway. So, you'd have to write it by hand regardless.
If Andrei intended his answer this way, I don't quite see how it would be an appropriate solution, as Andrej explicitly stated in his original post: »I see that as needlessly implementing in user-code what the compiler can already do on its own.«
[snip ginormous reply]
So you assumed initFromParameters would contain the initialization code for sum and average? In this case, using a mixin doesn't make any sense in the first place to me, since it would indeed be very specific to Foo and could be just a private function… David
Aug 08 2011
parent "Jonathan M Davis" <jmdavisProg gmx.com> writes:
 On 8/9/11 12:25 AM, Jonathan M Davis wrote:
 On 8/8/11 10:41 PM, Jonathan M Davis wrote:
 In this particular example, what goes in initFromParameters is specific
 to Foo anyway. So, you'd have to write it by hand regardless.
If Andrei intended his answer this way, I don't quite see how it would be an appropriate solution, as Andrej explicitly stated in his original post: »I see that as needlessly implementing in user-code what the compiler can already do on its own.«
[snip ginormous reply]
So you assumed initFromParameters would contain the initialization code for sum and average? In this case, using a mixin doesn't make any sense in the first place to me, since it would indeed be very specific to Foo and could be just a private function…
Well, it has to include that code or there was no point in creating the constructor in the first place, since as Andrej notes, Foo(1, 2, 3, 4) works withou declaring a constructor. So, all-in-all, I don't think that Andrei's suggestion works. If you mix in the entire constructor, then you can do it, but just doing a mixin inside of the constructor like Andrei suggested doesn't buy you anything over just putting it directly in the constructor as far as I can see, unless you have multiple constructors. But then the mixin probably wouldn't work anyway, since it was mixing in everything, not just the common stuff. And if all you're doing is the common stuff, then you might as well have a separate, private function to call. So, it looks like the best solution to this is to use either a string mixin or a template mixin to mix in the entire constructor where it's given the number of member variables to put in the constructor's parameters and the function to be the "post constructor" to call after the member variables have been initialized from the function parameters. - Jonathan M Davis
Aug 08 2011
prev sibling parent reply Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
Done deal:

import std.traits;

string FieldInit(T, int len, string fun)()
{
    string result;
    auto fields = [__traits(allMembers, T)];

    result ~= "this(";

    foreach (y, x; (RepresentationTypeTuple!T)[0..len])
    {
        result ~= x.stringof ~ " " ~ fields[y] ~ ", ";
    }

    result = result[0.. $-2] ~ ") { ";

    foreach (x; 0 .. len)
    {
        result ~= "this." ~ fields[x] ~ " = " ~ fields[x] ~ "; ";
    }

    result ~= fun ~ "();";
    result ~= "}";

    return result;
}

struct Foo
{
    int a;
    int b;
    int c;
    int d;

    mixin( FieldInit!(typeof(this), 4, "_this") );

    int sum;
    int average;

    void _this()
    {
        sum = a + b + c + d;
        average = (a + b + c + d) / 4;
    }
}

void main()
{
    auto foo = Foo(1, 2, 3, 4);

    assert(foo.sum == 10);
    assert(foo.average == 2);
}

Mostly.. Of course it doesn't work too good if the fields are placed
below some functions.
Aug 08 2011
parent reply KennyTM~ <kennytm gmail.com> writes:
On Aug 9, 11 05:05, Andrej Mitrovic wrote:
 Done deal:

 import std.traits;

 string FieldInit(T, int len, string fun)()
 {
      string result;
      auto fields = [__traits(allMembers, T)];

      result ~= "this(";

      foreach (y, x; (RepresentationTypeTuple!T)[0..len])
      {
          result ~= x.stringof ~ " " ~ fields[y] ~ ", ";
      }

      result = result[0.. $-2] ~ ") { ";

      foreach (x; 0 .. len)
      {
          result ~= "this." ~ fields[x] ~ " = " ~ fields[x] ~ "; ";
      }

      result ~= fun ~ "();";
      result ~= "}";

      return result;
 }

 struct Foo
 {
      int a;
      int b;
      int c;
      int d;

      mixin( FieldInit!(typeof(this), 4, "_this") );

      int sum;
      int average;

      void _this()
      {
          sum = a + b + c + d;
          average = (a + b + c + d) / 4;
      }
 }

 void main()
 {
      auto foo = Foo(1, 2, 3, 4);

      assert(foo.sum == 10);
      assert(foo.average == 2);
 }

 Mostly.. Of course it doesn't work too good if the fields are placed
 below some functions.
Template-mixin is often shorter and less error-prone. ----------------- mixin template FieldInit(size_t count, alias fun) { this(RepresentationTypeTuple!(typeof(this))[0..count] params) { foreach (y, x; __traits(allMembers, typeof(this))[0..count]) __traits(getMember, this, x) = params[y]; fun(); } } struct Foo { int a; int b; int c; int d; int sum; int average; void _this() { sum = a + b + c + d; average = (a + b + c + d) / 4; } mixin FieldInit!(4, _this); } -----------------
Aug 08 2011
parent Andrej Mitrovic <andrej.mitrovich gmail.com> writes:
On 8/8/11, KennyTM~ <kennytm gmail.com> wrote:
 Template-mixin is often shorter and less error-prone.
 -----------------

 mixin template FieldInit(size_t count, alias fun)
 {
      this(RepresentationTypeTuple!(typeof(this))[0..count] params)
      {
          foreach (y, x; __traits(allMembers, typeof(this))[0..count])
              __traits(getMember, this, x) = params[y];
          fun();
      }
 }
Yum! That's much sexier, thanks.
Aug 08 2011