Discussion:
struct and default constructor
deadalnix
2011-11-27 19:53:46 UTC
Permalink
Hi,

I wonder why struct can't have a default constructor. TDPL state that it
is required to allow every types to have a constant .init .

That is true, however not suffiscient. A struct can has a void[constant]
as a member and this doesn't have a .init . So this limitation does not
ensure that the benefit claimed is garanteed.

Additionnaly, if it is the only benefit, this is pretty thin compared to
the drawback of not having a default constructor.

We already have @disable this(); for structs. This cause the struct to
behave differently and cause the error ? initializer required for type
xxx ? when a variable of type xxx is defined without initialization.
This doesn't prevent the struct to get a constant .init and to use it
(even if this .init is probably garbage in this case, the whole point of
the @disable is to force the user to use a constructor or to copy).

The whole stuff seems very unclear and limitations arbitrary.

Note that a static opCall and a @disabel default constructor doesn't do
the trick as opCall can't be used with new to allocate on the heap. This
is only a cheap workaround.

Note also that xxx myvar = xxx.init; worls and doesn't trigger postblit.
So the @disable this(); is also a structure thatd oesn't garantee anything.

The point of classes and struct beeing different in D is slicing.
Default constructor is unrelated to that problem.

Why not allow both @disable this(); and default constructor construct,
both triggering the error ? initializer required for type xxx ? ? This
would make much more sense. Postblit should also be triggered on
assiagnation with .init to ensure the consistency of the whole construct.
deadalnix
2011-11-27 20:19:38 UTC
Permalink
In addition, here is a workaround :

// Wonderfull !
@disable this();

// Default constructor workaround.
this(int dummy = 0) { ... }

But that look very dirty and it feels like working against the language.
Andrej Mitrovic
2011-11-27 20:39:05 UTC
Permalink
That's not a workaround, that ctor never gets called unless you pass
an argument.
Andrej Mitrovic
2011-11-27 20:46:07 UTC
Permalink
Wait nevermind, I thought you mean't the ctor *actually runs*. What
you meant was disable this() doesn't work with that ctor in place.
Post by Andrej Mitrovic
That's not a workaround, that ctor never gets called unless you pass
an argument.
deadalnix
2011-11-27 20:52:19 UTC
Permalink
Post by Andrej Mitrovic
That's not a workaround, that ctor never gets called unless you pass
an argument.
So if you @disable this(); you shouldn't get an error. Actually, you
have an implicit constructor, even if it's only to set the variable as
equal to .init property of the struct.
Simen Kjærås
2011-11-27 21:24:15 UTC
Permalink
Post by deadalnix
// Wonderfull !
@disable this();
// Default constructor workaround.
this(int dummy = 0) { ... }
But that look very dirty and it feels like working against the language.
I believe your "workaround" will disappoint you, as it simply does not run.

import std.stdio;

struct A {
int value;
@disable this();
this(int dummy = 0) {
value = 3;
writeln("Hello from struct default constructor!"); // Never
happens.
}
}

void main() {
A a = A();
writeln( a.value ); // Writes 0
}

More importantly, this shows a bug in DMD - namely that structs with
@disabled default constructors can be created without a call to any
constructor and with no error message. In fact, if one removes the
constructor with dummy arguments, the program still compiles.

http://d.puremagic.com/issues/show_bug.cgi?id=7021
deadalnix
2011-11-27 21:43:13 UTC
Permalink
Post by Simen Kjærås
Post by deadalnix
// Wonderfull !
@disable this();
// Default constructor workaround.
this(int dummy = 0) { ... }
But that look very dirty and it feels like working against the language.
I believe your "workaround" will disappoint you, as it simply does not run.
import std.stdio;
struct A {
int value;
@disable this();
this(int dummy = 0) {
value = 3;
writeln("Hello from struct default constructor!"); // Never happens.
}
}
void main() {
A a = A();
writeln( a.value ); // Writes 0
}
More importantly, this shows a bug in DMD - namely that structs with
@disabled default constructors can be created without a call to any
constructor and with no error message. In fact, if one removes the
constructor with dummy arguments, the program still compiles.
http://d.puremagic.com/issues/show_bug.cgi?id=7021
So that is even worse that what I though. This struct situation is very
messy and inconsistent. We should definie what we want for that and have
a descent spec.

I just found something new : if you disable this(this), then opAssign
should be allowed (it is allowed, according to the website, when
implicit cast doesn't exists). The copy don't work with opAssign and
disabled postblit . . .
Steve Teale
2011-11-28 15:17:55 UTC
Permalink
That's not a workaround, that ctor never gets called unless you pass an
argument.
But it lets you pass a Range test.
dcrepid via Digitalmars-d
2014-10-10 01:32:51 UTC
Permalink
Hi,
I wonder why struct can't have a default constructor...
I know this is an old thread, but I've run into this same problem
recently and search yielded this result.

I myself have tried working around the default-constructor
problem with things like

this(bool bInit = true)

- which of course doesn't get invoked with MyStruct(), even with
@disable this.

This seems like a language limitation to me. I have a few
templated resource management objects that rely on RAII, but
sometimes there's no real use for arguments, especially if a
template defines what needs to be created. But now I'm forced to
both disable the default constructor AND to require unnecessary
parameters. Why? Another workaround is to use some object
factory mechanism, but that's just extra code to maintain.

Also, apparently doing cleanup in class object destructors is a
big no-no as resources that it may need to clean up could
potentially already be destroyed, depending on when the GC kicks
in and what objects are cleared by it. So no possibility of using
objects as resource acquire/release mechanisms.

I assume using scoped with class objects will have a similar
problem..
ketmar via Digitalmars-d
2014-10-10 08:03:39 UTC
Permalink
On Fri, 10 Oct 2014 01:32:51 +0000
Post by dcrepid via Digitalmars-d
This seems like a language limitation to me.
that is what we have to pay for the rule "type must always has
well-defined initial value".

the thing is that struct must have initial value that can be calculated
without executing any code. i.e. `T.init`. with default struct
constructor we can't have such initial state anymore. this is highly
unsafe.

yet compiler ignores constructor with default args when creating
struct. i.e. for this:

struct A {
@disable this ();
this (int v=42) { ... }
...
}
...
auto a = A();

compiler will not call this(42), but will generate error. i'm not sure
if this must be changed though, 'cause `this (int)` now becames default
constructor and we have no well-defined initial value anymore.
Post by dcrepid via Digitalmars-d
So no possibility of using
objects as resource acquire/release mechanisms.
I assume using scoped with class objects will have a similar
problem..
no, stack-allocated object is GC root, so anything it holds reference
to will not be destroyed until stack object is alive. so destructor of
stack-allocated object *can* be used for doing cleanup.

but you can use templates to build your "anchor" structs. it's hard to
say without concrete code samples.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141010/14aecc3f/attachment.sig>
via Digitalmars-d
2014-10-10 09:14:14 UTC
Permalink
Post by dcrepid via Digitalmars-d
Hi,
I wonder why struct can't have a default constructor...
I know this is an old thread, but I've run into this same
problem recently and search yielded this result.
I myself have tried working around the default-constructor
problem with things like
this(bool bInit = true)
- which of course doesn't get invoked with MyStruct(), even
You can use `static opCall` as a workaround. The following prints
"S(0)" and "S(3)":

struct S {
int x;
static S opCall() {
S s;
s.x = 3;
return s;
}
}

void main() {
import std.stdio;
S s;
writeln(s);
S t = S();
writeln(t);
}

But if you add a constructor with parameters, you get an error:

struct S {
int x;
static S opCall() { ... }
this(int y) { x = y; }
}

xx.d(4): Error: struct xx.S static opCall is hidden by
constructors and can never be called
xx.d(4): Please use a factory method instead, or replace
all constructors with static opCall.

IMO this is too restrictive, as obviously, the static opCall is
_not_ hidden by the constructor.
Dicebot via Digitalmars-d
2014-10-11 04:14:24 UTC
Permalink
Post by via Digitalmars-d
You can use `static opCall` as a workaround.
Please don't. It is never worth the gain. Never.
dcrepid via Digitalmars-d
2014-10-10 18:50:29 UTC
Permalink
On Friday, 10 October 2014 at 08:03:49 UTC, ketmar via
Post by ketmar via Digitalmars-d
On Fri, 10 Oct 2014 01:32:51 +0000
So no possibility of using objects as resource acquire/release
mechanisms.
I assume using scoped with class objects will have a similar
problem..
no, stack-allocated object is GC root, so anything it holds
reference
to will not be destroyed until stack object is alive. so
destructor of
stack-allocated object *can* be used for doing cleanup.
but you can use templates to build your "anchor" structs. it's
hard to
say without concrete code samples.
What I mean with the scoped objects is that you still need to
provide a constructor with parameters, yes? I will have to try
this out myself, but I've avoided it since some people seem to
indicate this is going to be deprecated..

Basically what I was doing code-wise was creating a 'FixedBuffer'
structure, which would have its size passed as a template
parameter. The constructor would do a malloc on it, and all the
checks for bounds would only need to rely on that template
parameter. Net result is that the cost of having a pretty safe
bounds-checked buffer was just the size of one pointer.

I *sorta* get the whole concept of being able to easily reason
and work with structures in D, but why then does it have a
destructor and a way to disable a default constructor, along with
operator overloads and whatnot. It seems to me its trying to be
object-like and POD-like at the same time which doesn't mesh well.

As far as the 'scoped' object, I was referring to default
construction - is it possible? Or do I need to use Walter's
factory production workaround everytime I want something with
deterministic create/destroy properties in this context?

I would very readily accept using objects over structs if they
had a guarantee of when they are destroyed, and weren't as risky
to pass around as C pointers (i.e. possibility of them being
null).
ketmar via Digitalmars-d
2014-10-10 20:25:05 UTC
Permalink
On Fri, 10 Oct 2014 18:50:29 +0000
Post by dcrepid via Digitalmars-d
What I mean with the scoped objects is that you still need to
provide a constructor with parameters, yes? I will have to try
this out myself, but I've avoided it since some people seem to
indicate this is going to be deprecated..
only the syntax `scope auto a = new A();` is deprecated, not the whole
concept. you just have to write it this now:

import std.typecons : scoped; // yep, it's in a library now
...
class A {
this () { ... }
~this () { ... }
}
...
{
// allocate class instance on the stack
auto a = scoped!A();
...
} // here destructor for 'a' will be called

just be careful and don't let 'a' escape the scope. ;-)
Post by dcrepid via Digitalmars-d
Basically what I was doing code-wise was creating a 'FixedBuffer'
structure, which would have its size passed as a template
parameter. The constructor would do a malloc on it, and all the
checks for bounds would only need to rely on that template
parameter. Net result is that the cost of having a pretty safe
bounds-checked buffer was just the size of one pointer.
it's better to use slices for that. you can create struct like this:

struct A(size_t asize) {
private ubyte* mData/* = null*/; // '=null' is the default
private enum mSize = asize;

@disable this (this);

~this () {
import core.stdc.stdlib;
if (mData !is null) free(mData);
}

// and allocate memory for data in getter
@property auto data () {
import core.stdc.stdlib;
if (mData is null) {
import core.exception : onOutOfMemoryError;
mData = cast(typeof(mData))malloc(mSize);
if (mData is null) onOutOfMemoryError();
}
return mData[0..mSize];
}

@property size_t length () const @safe pure nothrow @nogc { return mSize; }
size_t opDollar () const @safe pure nothrow @nogc { return mSize; }

ubyte opIndex (size_t ofs) @trusted {
import core.exception : RangeError;
if (ofs >= mSize) throw new RangeError();
return data[ofs];
}

ubyte opIndexAssign (ubyte v, size_t ofs) @trusted {
import core.exception : RangeError;
if (ofs >= mSize) throw new RangeError();
return (data[ofs] = v);
}
}

but then you will not be able to copy it (or you'll need additional
field to keep "don't free" flag, and with it you can use slice instead,
'cause they both will not fit into the pointer). and you will not be
able to pass it to functions anyway, this will not work:

void doSomethingOnBuffer (ref A buf);

only this:

void doSomethingOnBuffer (ref A!1024 buf);

i.e. you'll have to write the size in function args.
Post by dcrepid via Digitalmars-d
I *sorta* get the whole concept of being able to easily reason
and work with structures in D, but why then does it have a
destructor and a way to disable a default constructor, along with
operator overloads and whatnot. It seems to me its trying to be
object-like and POD-like at the same time which doesn't mesh well.
'cause there are two kinds of structs, actually. one kind is POD, and
another is object-like, with methods, destructors and so on. this may be
confusing a little. ;-) just don't mix 'em.
Post by dcrepid via Digitalmars-d
As far as the 'scoped' object, I was referring to default
construction - is it possible?
you can use classes, as i written above. just don't store such
instance anywhere, 'cause it will be overwritten with another data
after exiting the scope and all hell breaks loose.
Post by dcrepid via Digitalmars-d
I would very readily accept using objects over structs if they
had a guarantee of when they are destroyed, and weren't as risky
to pass around as C pointers (i.e. possibility of them being
null).
you can pass pointers to structs. pointers can be null. ;-)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141010/7595fead/attachment.sig>
dcrepid via Digitalmars-d
2014-10-10 21:45:32 UTC
Permalink
On Friday, 10 October 2014 at 20:25:16 UTC, ketmar via
Post by ketmar via Digitalmars-d
On Fri, 10 Oct 2014 18:50:29 +0000
Post by dcrepid via Digitalmars-d
What I mean with the scoped objects is that you still need to
provide a constructor with parameters, yes? I will have to try
this out myself, but I've avoided it since some people seem to
indicate this is going to be deprecated..
only the syntax `scope auto a = new A();` is deprecated, not
the whole
import std.typecons : scoped; // yep, it's in a library now
...
class A {
this () { ... }
~this () { ... }
}
...
{
// allocate class instance on the stack
auto a = scoped!A();
...
} // here destructor for 'a' will be called
just be careful and don't let 'a' escape the scope. ;-)
Great, thanks for the tips!
Post by ketmar via Digitalmars-d
it's better to use slices for that. you can create struct like
(snipped)
Thanks for the effort of providing a proof of concept, even
though I don't agree with what the data property should do.
Shouldn't properties not mutate the data? But more than that I
think its a waste to check whether the buffer is there every time
you need to access it. Its better to allocate at the start, throw
an exception if it can't be allocated, then provide access to it
without any wasteful checks between. At least, this is the RAII
type of idiom I'm after.

How I am currently using it is using either .ptr, .data, or
slices to create the necessary access or D slices. It works
pretty well for what I'm using it for. I'm interfacing with the
Windows API with the .ptr property, and then when I need to use
it with D functions I typically use opSlice since the data is
often variable lengths.

I've pasted most of the struct I've been using here:
http://dpaste.dzfl.pl/15381d5828f8

I would use it in say, a call to Windows' WinHttpReadData() using
OutBuffer.ptr, then work with the received data (stored in
dwDownloaded) using OutBuffer[0 .. dwDownloaded], for example. It
works pretty nicely even if its not up to D's standards.

About escaping scope - I'm aware this is possible, but the idea
is that this is supposed to be used temporarily in a certain
scope, then discarded (as the Scope prefix indicates).. there's
better methods to doing it safely, for sure. But to do the same
with only a single pointer?

I think I like the idea of the factory method now though, as I've
learned you can hide a struct inside a function, and then call
the function to set the struct up properly and return it. At
least, I'm sure I've seen code that does that..
Post by ketmar via Digitalmars-d
Post by dcrepid via Digitalmars-d
It seems to me its trying to be object-like and POD-like at
the same time which doesn't mesh well.
'cause there are two kinds of structs, actually. one kind is
POD, and
another is object-like, with methods, destructors and so on.
this may be
confusing a little. ;-) just don't mix 'em.
Haha.. I'll try not to (I think?)
Post by ketmar via Digitalmars-d
Post by dcrepid via Digitalmars-d
I would very readily accept using objects over structs if they
had a guarantee of when they are destroyed, and weren't as
risky to pass around as C pointers (i.e. possibility of them
being null).
you can pass pointers to structs. pointers can be null. ;-)
I thought ref gives us the same guarantee that C++'s references
do? Pointers are so.. 90's =P But yeah, the nullable object
thing has bitten my ass a few times as I'm learning D, which
really frustrates me =\

Thanks for your time
ketmar via Digitalmars-d
2014-10-10 22:34:34 UTC
Permalink
On Fri, 10 Oct 2014 21:45:32 +0000
Post by dcrepid via Digitalmars-d
Great, thanks for the tips!
you're welcome. ;-)
Post by dcrepid via Digitalmars-d
Thanks for the effort of providing a proof of concept, even
though I don't agree with what the data property should do.
Shouldn't properties not mutate the data?
it depends. ;-) i think that doing such "lazy initialization" in
property is acceptable. maybe not the best style, but acceptable. yet i
may be wrong, of course.
Post by dcrepid via Digitalmars-d
I think its a waste to check whether the buffer is there every time
you need to access it.
this check costs almost nothing. and when you need buffer, most of the
time you need it for some lengthy operation anyway, so checking cost
can be ignored. and you can do `auto mypointer = a.data.ptr;` to avoid
further checks. actually, it's meant to be used like this:

auto d = a.data;
// here we'll work with d, so no more checks
Post by dcrepid via Digitalmars-d
Its better to allocate at the start, throw
an exception if it can't be allocated, then provide access to it
without any wasteful checks between. At least, this is the RAII
type of idiom I'm after.
the main caveat with lazy initialization is that your program can fail
at arbitrary place (where you'll first access .data), not when you
declaring buffer this can be bad 'cause you may already execute some
preparation code thinking that you have your buffer at hand.
Post by dcrepid via Digitalmars-d
http://dpaste.dzfl.pl/15381d5828f8
will take a closer look at it later, i'm sleepy now. ;-)

btw, i'm used to call core.exception.onOutOfMemoryError when malloc()
fails. We can't be sure that we still have memory to construct new
Error object. sure, we'll lose linenumber info this way. it's not a
rule of thumb, though (Phobos tend to throw new OutOfMemoryError
instances, for example). but if we are working with memory on such low
level...
Post by dcrepid via Digitalmars-d
better methods to doing it safely, for sure. But to do the same
with only a single pointer?
why do you insisting on having single pointer? sure you can use all
sort of tricks to pack alot of data in single pointer, but i can't see
any practical sense in it. today when smartphones have gigabytes of
RAM, i'll trade some more pointers for ease of using and safety.
Post by dcrepid via Digitalmars-d
I think I like the idea of the factory method now though, as I've
learned you can hide a struct inside a function, and then call
the function to set the struct up properly and return it. At
least, I'm sure I've seen code that does that..
ah, yes, it's "Voldemort type". ;-)

auto thatWhoCantBeNamed () {
static struct A { ... }
return A();
}

voila. you have type that you can use but cannot create without
factory. but you need to have postblit enabled with this.
Post by dcrepid via Digitalmars-d
Post by ketmar via Digitalmars-d
you can pass pointers to structs. pointers can be null. ;-)
I thought ref gives us the same guarantee that C++'s references
do? Pointers are so.. 90's =P But yeah, the nullable object
thing has bitten my ass a few times as I'm learning D, which
really frustrates me =\
i've never used nullable myself. i'm just constantly forgetting about
it (and about ~80% of Phobos for that matter ;-).
Post by dcrepid via Digitalmars-d
Thanks for your time
your posts made me think about some things ;-), so thanks for your time
too.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141011/09f02924/attachment.sig>
Nicolas Sicard via Digitalmars-d
2014-10-11 08:41:15 UTC
Permalink
On Friday, 10 October 2014 at 22:34:45 UTC, ketmar via
Post by ketmar via Digitalmars-d
Post by dcrepid via Digitalmars-d
I think I like the idea of the factory method now though, as
I've learned you can hide a struct inside a function, and then
call the function to set the struct up properly and return it.
At least, I'm sure I've seen code that does that..
ah, yes, it's "Voldemort type". ;-)
auto thatWhoCantBeNamed () {
static struct A { ... }
return A();
}
voila. you have type that you can use but cannot create without
factory. but you need to have postblit enabled with this.
It's not a "Voldemort type" if the struct A is static, is it? You
can just alias A = typeof(thatWhoCantBeNamed()); and then create
a new object of the same type.

--
Nicolas
ketmar via Digitalmars-d
2014-10-11 09:08:20 UTC
Permalink
On Sat, 11 Oct 2014 08:41:15 +0000
Post by Nicolas Sicard via Digitalmars-d
On Friday, 10 October 2014 at 22:34:45 UTC, ketmar via
Post by ketmar via Digitalmars-d
Post by dcrepid via Digitalmars-d
I think I like the idea of the factory method now though, as
I've learned you can hide a struct inside a function, and then
call the function to set the struct up properly and return it.
At least, I'm sure I've seen code that does that..
ah, yes, it's "Voldemort type". ;-)
auto thatWhoCantBeNamed () {
static struct A { ... }
return A();
}
voila. you have type that you can use but cannot create without
factory. but you need to have postblit enabled with this.
It's not a "Voldemort type" if the struct A is static, is it? You
can just alias A = typeof(thatWhoCantBeNamed()); and then create
a new object of the same type.
yes, my bad. i'm so used to write "static" to avoid the things that
compiler can remove for me that my code is polluted with it.

thank you.
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141011/9b3682f8/attachment.sig>
dcrepid via Digitalmars-d
2014-10-11 08:47:59 UTC
Permalink
On Friday, 10 October 2014 at 22:34:45 UTC, ketmar via
Post by ketmar via Digitalmars-d
On Fri, 10 Oct 2014 21:45:32 +0000
Post by dcrepid via Digitalmars-d
Shouldn't properties not mutate the data?
it depends. ;-) i think that doing such "lazy initialization" in
property is acceptable. maybe not the best style, but
acceptable. yet i
may be wrong, of course.
Ah, I see. Lazy initialization is a nice approach to some things.
But I'm not entirely sure it belongs in the structure or object
itself.. however, if I were too I might put it in a 'length' or
'rezise' member function.. but that'd be better for a resizable
object..
Post by ketmar via Digitalmars-d
Post by dcrepid via Digitalmars-d
better methods to doing it safely, for sure. But to do the
same with only a single pointer?
why do you insisting on having single pointer? sure you can use
all
sort of tricks to pack alot of data in single pointer, but i
can't see
any practical sense in it. today when smartphones have
gigabytes of
RAM, i'll trade some more pointers for ease of using and safety.
Actually, I think memory efficiency and speed are pretty key
today, especially with embedded systems, server farms, and mobile
devices. And it doesn't just have to do with available memory,
but cache lines, register usage, etc. But that's another debate
for another time.

As far as the safety tradeoff though.. I prevent copying and
default construction, so at least two problems should
theoretically be solved. The possibility of using ranges or
pointers outside of the function or scope is the only thing that
would need to be controlled for. And there I suppose the GC or
reference counting mechanisms would be the only other options.
But hopefully if I document it right, and suggest the correct
alternatives to use, I can prevent careless programmers from
making those mistakes. (I think std.container.array.Array is a
nice fallback)
Post by ketmar via Digitalmars-d
ah, yes, it's "Voldemort type". ;-)
Ah, I had wondered why/where/what context I heard that under!
Makes more sense now. =)

Also, thanks for the other pointers (I didnt quote everything
here)
Walter Bright via Digitalmars-d
2014-10-10 09:58:39 UTC
Permalink
I wonder why struct can't have a default constructor. TDPL state that it is
required to allow every types to have a constant .init .
Having a .init instead of a default constructor has all kinds of useful properties:

1. the default object is trivially constructable and cannot fail

2. an easy way for other constructors to not have overlooked field initializers,
so they get garbage initialized like in C++

3. generic code can rely on the existence of trivial construction that cannot fail

4. dummy objects can be created, useful for "does this compile" semantics

5. an object can be "destroyed" by overwriting it with .init (overwriting with 0
is not the same thing)

6. when you see:
T t;
in code, you know it is trivially constructed and will succeed

7. objects can be created using TypeInfo


Default constructors are baked into C++. I can't escape the impression that the
desire for D default constructors comes from more or less trying to write C++
style code in D.

I feel that non-trivial default construction is a bad idea, as are the various
methods people try to get around the restriction. For non-trivial construction,
one can easily just make a constructor with an argument, or call a factory
method that returns a constructed object.
Szymon Gatner via Digitalmars-d
2014-10-10 10:23:56 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by deadalnix
I wonder why struct can't have a default constructor. TDPL
state that it is
required to allow every types to have a constant .init .
Having a .init instead of a default constructor has all kinds
1. the default object is trivially constructable and cannot fail
2. an easy way for other constructors to not have overlooked
field initializers, so they get garbage initialized like in C++
3. generic code can rely on the existence of trivial
construction that cannot fail
4. dummy objects can be created, useful for "does this compile" semantics
5. an object can be "destroyed" by overwriting it with .init
(overwriting with 0 is not the same thing)
T t;
in code, you know it is trivially constructed and will succeed
7. objects can be created using TypeInfo
Default constructors are baked into C++. I can't escape the
impression that the desire for D default constructors comes
from more or less trying to write C++ style code in D.
Bit OT: What is The D code style then? It would be very useful
for those coming from C++ to have a wiki/article on how to
translate C++ idioms and practices to D. I too am writing D code
like I do my C++ and often find myself puzzled (deterministic
d-tors being perfect example). Missing default struct c-tor is
also one of such examples - and adding opCall() feels hacky.
Walter Bright via Digitalmars-d
2014-10-10 16:23:03 UTC
Permalink
Bit OT: What is The D code style then? It would be very useful for those coming
from C++ to have a wiki/article on how to translate C++ idioms and practices to
D. I too am writing D code like I do my C++ and often find myself puzzled
(deterministic d-tors being perfect example). Missing default struct c-tor is
also one of such examples - and adding opCall() feels hacky.
You're right, but it's a bit of a difficult question to answer. Other examples
that cause people grief:

1. using ref as a type constructor
2. multiple inheritance
3. using a struct as both a value and a polymorphic type

These are idiomatic C++ usages that need to be rethought for D.
ketmar via Digitalmars-d
2014-10-10 10:25:01 UTC
Permalink
On Fri, 10 Oct 2014 02:58:39 -0700
Post by Walter Bright via Digitalmars-d
Default constructors are baked into C++. I can't escape the
impression that the desire for D default constructors comes from more
or less trying to write C++ style code in D.
second this. people also keep forgetting about default values and want
default constructors to simulate this:

struct {
int n = 42;
string s = "default string";
}
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141010/c10fc6fa/attachment.sig>
Walter Bright via Digitalmars-d
2014-10-10 16:24:00 UTC
Permalink
Post by ketmar via Digitalmars-d
people also keep forgetting about default values and want
struct {
int n = 42;
string s = "default string";
}
That's a good observation.
Simon A via Digitalmars-d
2014-10-11 00:25:49 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by deadalnix
I wonder why struct can't have a default constructor. TDPL
state that it is
required to allow every types to have a constant .init .
Having a .init instead of a default constructor has all kinds
1. the default object is trivially constructable and cannot fail
2. an easy way for other constructors to not have overlooked
field initializers, so they get garbage initialized like in C++
3. generic code can rely on the existence of trivial
construction that cannot fail
4. dummy objects can be created, useful for "does this compile" semantics
5. an object can be "destroyed" by overwriting it with .init
(overwriting with 0 is not the same thing)
T t;
in code, you know it is trivially constructed and will succeed
7. objects can be created using TypeInfo
Default constructors are baked into C++. I can't escape the
impression that the desire for D default constructors comes
from more or less trying to write C++ style code in D.
I feel that non-trivial default construction is a bad idea, as
are the various methods people try to get around the
restriction. For non-trivial construction, one can easily just
make a constructor with an argument, or call a factory method
that returns a constructed object.
I've often thought that most of the hidden pain of arbitrary
constructors in C++ could be avoided if C++ could take advantage
of functional purity.

D has native functional purity. Couldn't you get the same
benefits that you listed by allowing default constructors but
requiring them to be pure? Of course, you can get the same
outcome by initialising members using static pure functions or
various other helpers, so you could say that pure default
constructors are just syntactic sugar.

Pure default constructors do have some advantages for more
complex construction, though. For the sake of example, say I
have a struct that uses a table of complex numbers, and for
unrelated reasons the real and imaginary parts are stored in
separate arrays. I.e., I have to initialise multiple members
using one calculation. Adding pure default constructors to D
would allow this to be implemented more cleanly and more
intuitively.
Walter Bright via Digitalmars-d
2014-10-11 04:41:54 UTC
Permalink
Post by Simon A via Digitalmars-d
D has native functional purity. Couldn't you get the same
benefits that you listed by allowing default constructors but
requiring them to be pure?
I suspect that CTFE can accomplish most of that today - with the exception that
CTFE will not allocate runtime memory for you.
Dicebot via Digitalmars-d
2014-10-11 04:43:22 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by Simon A via Digitalmars-d
D has native functional purity. Couldn't you get the same
benefits that you listed by allowing default constructors but
requiring them to be pure?
I suspect that CTFE can accomplish most of that today - with
the exception that CTFE will not allocate runtime memory for
you.
There is ER somewhere in bugzilla AFAIR about allowing CTFE-only
struct default constructors.
Walter Bright via Digitalmars-d
2014-10-11 05:49:13 UTC
Permalink
Post by Walter Bright via Digitalmars-d
Post by Simon A via Digitalmars-d
D has native functional purity. Couldn't you get the same
benefits that you listed by allowing default constructors but
requiring them to be pure?
I suspect that CTFE can accomplish most of that today - with the exception
that CTFE will not allocate runtime memory for you.
There is ER somewhere in bugzilla AFAIR about allowing CTFE-only struct default
constructors.
Note that you can do (as pointed out upthread):

struct S {
int x = 7;
string s = "hello";
}

which then has default initialization. Of course, CTFE will work on those rvalues.
Jonathan M Davis via Digitalmars-d
2014-10-11 06:09:16 UTC
Permalink
Post by via Digitalmars-d
There is ER somewhere in bugzilla AFAIR about allowing CTFE-only struct
default constructors.
struct S {
int x = 7;
string s = "hello";
}
which then has default initialization. Of course, CTFE will work on those rvalues.
The only reason that I can think of to have default constructors for filling
in the member variables during CTFE would be if you wanted to calculate some
of the values based on other values, and while that might be nice upon
occasion, I don't think that not having it is much of a loss.

- Jonathan M Davis
dcrepid via Digitalmars-d
2014-10-11 08:53:08 UTC
Permalink
Okay, I'm still kind of new to this language, so forgive me if I
see things a little differently than long time users..

Here's what I see though.

With Walter's description of what structures should be, it seems
to me to be slightly at odds with what D structures are capable
of, and in fact what they are being used for in a number of
places ('fixing' GC, adding reference counting, providing RAII,
etc).

Structs are classically POD types, as we know from C. But with D
you've added overloaded constructors, destructors, member
functions etc. To me that more or less defines an object, albeit
one with special limits. If you don't want to call them objects,
thats fine. But the fact that they have customizable behavior in
creation, copying, moving, and destruction means that they are
much more than just a plain old data type with generic T.init
states. They are a kind of living object, yea?

And it seems to me that the default constructor is just the last
component left to cement the structure as an object. It seems
curious that you would allow the ability to disable default
construction and copy construction, and allow hiding of
constructors, etc.. but then determine that default constructors
are going too far?

Sure, having a guarantee that a struct will always be created
without a fail state is a great concept. But why then haven't
they just been kept as a collection of data types, and another
'scope object' been created instead?

It seems what has happened is a FrankenStruct monster was created
in the midst of trying to find some midway point between D
objects and 'scope objects'..

I would myself love to use objects all the time, but I see these
problems that currently only the FrankenStructs resolve:

1. Objects are basically pointers, and can be null pointers at
that! (To me, that's C-levels of safety there)
However: Structs are always created.
2. Without assigning something to an object, there's obviously no
real initialization.
However: Structs are initialized on creation.
3. Object Destructors are called 'sometime in the future' when GC
runs. Cleaning up resources is discouraged as those resources
might have been cleaned up by the GC?
However: Struct destructors run when they are out of scope.
4. Objects always have at least one level of indirection (and a
lot of extra data)
Structs are there where you define them. (with no extra data)

I may not be 100% on all of this, I'm still learning. But many
times browsing the forums, reading the D books etc, I see the
solutions to D object problems becoming this: wrap it with a
struct. So whether its something like RefCounted, Scoped,
NonNullable, UniqueArray etc.. the problems with D objects are
fixed by relying on the guarantees provided by structures to
solve them.

I dunno, to me it all seems a bit discordant with what Walter
seems to want them to be?

(Sorry in advance if any of this comes off as mean or provoking..
I'm just sharing how I view the language design)
ketmar via Digitalmars-d
2014-10-11 09:19:33 UTC
Permalink
On Sat, 11 Oct 2014 08:53:08 +0000
dcrepid via Digitalmars-d <digitalmars-d at puremagic.com> wrote:

structs also can't be inherited and can't have virtual methods
(obviously, as they have no VMT). just a comment. ;-)

ah, and don't forget another great aspect of structs: confusing C++
users! ;-)
-------------- next part --------------
A non-text attachment was scrubbed...
Name: signature.asc
Type: application/pgp-signature
Size: 181 bytes
Desc: not available
URL: <http://lists.puremagic.com/pipermail/digitalmars-d/attachments/20141011/adacf4a4/attachment.sig>
dcrepid via Digitalmars-d
2014-10-11 21:32:23 UTC
Permalink
On Saturday, 11 October 2014 at 09:19:47 UTC, ketmar via
Post by ketmar via Digitalmars-d
On Sat, 11 Oct 2014 08:53:08 +0000
structs also can't be inherited and can't have virtual methods
(obviously, as they have no VMT). just a comment. ;-)
Yeah, thats one of the things I meant by "with special limits". =)
monarch_dodra via Digitalmars-d
2014-10-10 21:19:01 UTC
Permalink
Post by deadalnix
Hi,
I wonder why struct can't have a default constructor. TDPL
state that it is required to allow every types to have a
constant .init .
That is true, however not suffiscient. A struct can has a
void[constant] as a member and this doesn't have a .init . So
this limitation does not ensure that the benefit claimed is
garanteed.
Additionnaly, if it is the only benefit, this is pretty thin
compared to the drawback of not having a default constructor.
Think the argument is that declaring `T t;` must be CTFE, which
kind of implies a T.init state (which may have non-deterministic
values in the presence of " = void").

This is mostly for declaring things static, and the whole
"constructors are run after the .init blit".

But even then:
T t; //value is T.init, if not @disable this()
T t = T(); //Potentially run-time

I've started threads and tried to start discussions about this
before, but to no avail. It's a relativelly recurrent complain,
especially from "newer" C++ users. The older D users have either
of thrown in the towel, or implemented "workarounds".
Nick Treleaven via Digitalmars-d
2014-10-12 16:31:38 UTC
Permalink
Think the argument is that declaring `T t;` must be CTFE, which kind of
implies a T.init state (which may have non-deterministic values in the
presence of " = void").
This is mostly for declaring things static, and the whole "constructors
are run after the .init blit".
T t = T(); //Potentially run-time
I've started threads and tried to start discussions about this before,
but to no avail. It's a relativelly recurrent complain, especially from
"newer" C++ users.
Totally agree, and it doesn't help that some D proponents ignore this
point and just repeat "it doesn't work with the design of D". Whenever
there is a weak explanation for something it will damage D, to some
extent. In those cases it would be much better if a convincing argument
can be written up as a website FAQ, or at least state that more research
is needed before a solution can be tried.

Continue reading on narkive:
Loading...