Discussion:
Using in as a parameter qualifier
Shriramana Sharma
2013-05-31 05:36:07 UTC
Permalink
Hello people.

I have a pair type defined as :

struct pair {
double x,y ;
this () {}
this (double x, double y) { this.x = x ; this.y = y ; }
}

Consider a function that operates on a pair:

double abs2 ( pair a ) { return a.x * a.x + a.y * a.y ; }

In C++ the function signature would be: double abs2 ( const pair & a )

So I thought const ref pair a would be appropriate in D -- is that right?

How about the "in" qualifier? Does it only replace C++'s const in this
case or does it also guarantee that the object will not be copied
(&/ref) ?

Also: does D not require member initializer lists like in C++? In C++
I would write the this(double,double) above as: pair(double x, double
y):x(x),y(y){} -- since that language is supposed to guarantee that
once we enter the { of the constructor, all sub-objects are
initialized.

Thanks!
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Ali Çehreli
2013-05-31 06:42:41 UTC
Permalink
Post by Shriramana Sharma
struct pair {
It is more common to start type names with a capital letter: Pair.
Post by Shriramana Sharma
double x,y ;
this () {}
That is not allowed. In D, every type has the .init property, which is
its default value.
Post by Shriramana Sharma
this (double x, double y) { this.x = x ; this.y = y ; }
}
That is sensible to ensure that both values are provided. Otherwise, the
following struct definition is sufficient:

struct Pair {
double x, y;
}

void main()
{
auto p0 = Pair();
auto p1 = Pair(1.5); // only p1.y is 0
auto p2 = Pair(2.5, 3.5);
}

You can also set the .init value of the whole type:

struct Pair {
double x = 10.75;
double y = 20.25;
}
Post by Shriramana Sharma
double abs2 ( pair a ) { return a.x * a.x + a.y * a.y ; }
In C++ the function signature would be: double abs2 ( const pair & a )
So I thought const ref pair a would be appropriate in D -- is that right?
Yes:

double abs2(ref const(Pair) a) { /* ... */ }
Post by Shriramana Sharma
How about the "in" qualifier? Does it only replace C++'s const in this
case or does it also guarantee that the object will not be copied
(&/ref) ?
'in' is nothing but 'scope const' (scope is not implemented yet):

double abs2(in Pair a) { /* ... */ }

Of course, pass-by-vale (the other C++ option) is also possible:

double abs2(Pair a) { /* ... */ }

This one has the added benefit of compiler's automatic moving of the
value to the function. You know that 'a' is a copy of the argument so
you can safely move it for use later.
Post by Shriramana Sharma
Also: does D not require member initializer lists like in C++? In C++
I would write the this(double,double) above as: pair(double x, double
y):x(x),y(y){} -- since that language is supposed to guarantee that
once we enter the { of the constructor, all sub-objects are
initialized.
D guarantees that every member is already initialized by their .init
value when the constructor is entered (unless the initial value is
'void'). There are optimizations opportunities through flow analysis but
the compiler does not apply them yet.

Ali
Shriramana Sharma
2013-05-31 09:40:01 UTC
Permalink
Post by Shriramana Sharma
double x,y ;
this () {}
That is not allowed. In D, every type has the .init property, which is its
default value.
Hm can you clarify that a bit? If I actually try it I get:

pair.d(14): Error: constructor pair.pair.this default constructor for
Post by Shriramana Sharma
double abs2 ( pair a ) { return a.x * a.x + a.y * a.y ; }
In C++ the function signature would be: double abs2 ( const pair & a )
So I thought const ref pair a would be appropriate in D -- is that right?
double abs2(ref const(Pair) a) { /* ... */ }
But is this the idiomatic D way of doing things? I mean, would one
normally prefer in or ref const(T) which somehow seems more awkward
than even const T &?
Does that mean "this is const within the current scope"?

And does "in" *not* guarantee that the object is *not* copied? I mean,
if a parameter input to a function is read-only, it makes optimization
sense to not copy it but just automatically provide a reference to it
right? So I would expect in to mean const ref -- doesn't it work that
way, and if not, why not?
double abs2(Pair a) { /* ... */ }
This one has the added benefit of compiler's automatic moving of the value
to the function. You know that 'a' is a copy of the argument so you can
safely move it for use later.
I don't understand; can you please clarify that. I understand that a
is a copy of whatever pair is passed to it, so where do I move it and
why would I want to use it later?
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Jonathan M Davis
2013-05-31 15:25:27 UTC
Permalink
Post by Shriramana Sharma
Post by Ali Çehreli
Post by Shriramana Sharma
double abs2 ( pair a ) { return a.x * a.x + a.y * a.y ; }
In C++ the function signature would be: double abs2 ( const pair & a )
So I thought const ref pair a would be appropriate in D -- is that right?
double abs2(ref const(Pair) a) { /* ... */ }
But is this the idiomatic D way of doing things? I mean, would one
normally prefer in or ref const(T) which somehow seems more awkward
than even const T &?
In D, const ref does not accept rvalues (unlike C++'s const &). So, if you use
const ref, odds are that you need to duplicate the function

double abs2(ref const(Pair) a) {...}
double abs2(const Pair a) { abs2(a); /* calls the const ref overload */ }

If a function is templated, you can use auto ref to avoid the duplication

double abs2()(auto ref Pair a) {...}

or if you want const

double abs2()(auto ref const Pair a) {...}

auto ref is not currently implemented for non-templated functions, so if you
want to use it, you have to templatize the function (the two examples above
have empty template parameter lists just to templatize the function). auto ref
makes it so that the function will accept both lvalues and rvalues (it
generates a function that accepts by ref for lvalues and one which accepts by
value for rvalues).

I would advise against ever using in. It's an alias for const scope, and scope
is not currently fully implemented. scope is intended to disallow escaping
from the function. So, with a non-reference type, using scope is pointless.
And with a reference type, if/when scope is fully implemented later, you could
end up with compilation errors because you _were_ escaping a scope parameter
(e.g. by returning it or by assigning it to a static variable). At present,
scope is at least partially implemented with delegates, but that's it. So, I'd
advise against using scope (and thus in) with anything other than delegates.
The primary benefit of using it with delegates is it allows the compiler to
avoid allocating a closure when the function is called.
Post by Shriramana Sharma
Does that mean "this is const within the current scope"?
And does "in" *not* guarantee that the object is *not* copied? I mean,
if a parameter input to a function is read-only, it makes optimization
sense to not copy it but just automatically provide a reference to it
right? So I would expect in to mean const ref -- doesn't it work that
way, and if not, why not?
in most definitely does _not_ guarantee that the object is not copied. Quite
the opposite. It's an alias for const scope. If you want to avoid the copy,
you need to use ref. in meant something slightly different in D1, and it was
kept around for D2 but with the differences in the type system between them, it
was decided to make it an alias for const scope. Personally, I wish that it
was just gotten rid of outright due to the issues with scope and the confusion
that it causes when people want to know what in does, but I guess that they
wanted to reduce how much code broke when porting from D1 to D2.

- Jonathan M Davis
Shriramana Sharma
2013-05-31 23:41:44 UTC
Permalink
Post by Jonathan M Davis
In D, const ref does not accept rvalues (unlike C++'s const &).
So what is the syntax that accepts rvalues and yet does not make a
copy of the input object? I mean, as Ali says OK profiling is
preferable to find out where exactly passing by value is more
efficient than passing by const ref, but I'd like to know anyway what
is the syntax available for this so at least until I get my D legs, I
can continue some of my practices from C++ without much detriment.

I just finished reading this link which Ali gave:
http://bartoszmilewski.com/2008/11/03/who-ordered-rvalue-references-part-3/
but perhaps due to my ignorance I don't get 100% what he is tryring to
say. Is it really relevant for what I am asking, like avoiding a
copy-constructor in the input of a function which will not modify its
input, even if I pass by value?

I'm currently working on a project at some priority, and it's
currently at a relatively flexible stage so I am thinking of switching
it over to D thinking it would make me finish the job quicker, but if
I will have to bend my thinking too much around differences between
C++ and D and around unfinished language features (scope?) I think I
may stick with C++ at least for the present project and maybe use D
for future ones. Advice?
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Shriramana Sharma
2013-05-31 23:45:22 UTC
Permalink
Post by Shriramana Sharma
So what is the syntax that accepts rvalues and yet does not make a
copy of the input object? I mean, as Ali says OK profiling is
preferable to find out where exactly passing by value is more
efficient than passing by const ref, but I'd like to know anyway what
is the syntax available for this so at least until I get my D legs, I
can continue some of my practices from C++ without much detriment.
And again, sorry if I'm being dumb, but is the proposed inout syntax
intended to fix this problem or some other problem?
http://d.puremagic.com/issues/show_bug.cgi?id=3748 is marked as
resolved fixed from back in 2011, but if so why did DConf *this year*
have a talk on this issue?!
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Jonathan M Davis
2013-06-01 01:16:21 UTC
Permalink
Post by Shriramana Sharma
Post by Shriramana Sharma
So what is the syntax that accepts rvalues and yet does not make a
copy of the input object? I mean, as Ali says OK profiling is
preferable to find out where exactly passing by value is more
efficient than passing by const ref, but I'd like to know anyway what
is the syntax available for this so at least until I get my D legs, I
can continue some of my practices from C++ without much detriment.
And again, sorry if I'm being dumb, but is the proposed inout syntax
intended to fix this problem or some other problem?
http://d.puremagic.com/issues/show_bug.cgi?id=3748 is marked as
resolved fixed from back in 2011, but if so why did DConf *this year*
have a talk on this issue?!
inout is completely unrelated. It has to do with having a function which
accepts mutable, const, and immutable and returns the same level mutability
without having to duplicate your function. You don't want to have to have
something like

int[] foo(int[] bar)
{
... do something
return bar[i .. j];
}

const(int)[] foo(const(int)[] bar)
{
... do something
return bar[i .. j];
}


immutable(int)[] foo(immutable(int)[] bar)
{
... do something
return bar[i .. j];
}

Without inout, you're forced to do this kind of code duplication when you want
to accept multiple types of mutability and return the same mutability. You
could just accept const, but then you'd have to return const rather than the
original type which could also have been mutable or immutable.

const(int)[] foo(const(int)[] bar)
{
... do something
return bar[i .. j];
}

inout solves that problem:

inout(int)[] foo(inout(int)[] bar)
{
... do something
return bar[i .. j];
}

You only have to write the function once, and it works with all 3 levels of
mutability.

- Jonathan M Davis
Jonathan M Davis
2013-06-01 01:15:17 UTC
Permalink
Post by Shriramana Sharma
Post by Jonathan M Davis
In D, const ref does not accept rvalues (unlike C++'s const &).
So what is the syntax that accepts rvalues and yet does not make a
copy of the input object?
If you have a templated function, you can use auto ref

auto foo(T)(auto ref T var) {..}

or if you don't want to templatize the type but just the function:

auto foo()(auto ref T var) {...}

For non-templated functions, there currently is no solution. You're forced to
duplicate the function. Various options are under discussion, and it's likely
that auto ref will be used but with slightly different semantics (auto ref on
templated functions actually results in two functions - one with ref and one
without - and that won't work with non-templated functions). But that hasn't
been agreed upon yet. We _will_ have a solution, but it's a hot topic that's
still under debate. For various technical reasons, we won't go the C++ route
of having const ref accept rvalues (which is usually what people suggest), but
we will have a solution relatively soon as there's a fair bit of pressure to
resolve the problem.

- Jonathan M Davis
Ali Çehreli
2013-06-01 01:15:07 UTC
Permalink
Post by Shriramana Sharma
And again, sorry if I'm being dumb, but is the proposed inout syntax
intended to fix this problem or some other problem?
Think of 'inout' as a placeholder for one of the following three:

- const
- immutable
- (no const, nor immutable)

inout is deduced at call time and all of the inouts inside a function
and even the return type become the qualifier of the argument:

inout(int)[] firstHalf(inout(int)[] a)
{
return a[0..$/2];
}

void main()
{
int[] m;
const(int)[] c;
immutable(int)[] i;

assert(is (typeof(firstHalf(m)) == int[]));
assert(is (typeof(firstHalf(c)) == const(int)[]));
assert(is (typeof(firstHalf(i)) == immutable(int)[]));
}

That is a very handy feature for exactly situations like the above.
Post by Shriramana Sharma
why did DConf *this year* have a talk on this issue?!
The previous DConf was in 2007. It will be a yearly event from now on.

Ali
Shriramana Sharma
2013-06-01 03:38:51 UTC
Permalink
Thanks Jonathan and Ali for all those clarifications.

--
Shriramana Sharma ஶ்ரீரமணஶர்ம�
Ali Çehreli
2013-06-01 01:09:10 UTC
Permalink
On Fri, May 31, 2013 at 8:55 PM, Jonathan M Davis
Post by Jonathan M Davis
In D, const ref does not accept rvalues (unlike C++'s const &).
So what is the syntax that accepts rvalues and yet does not make a
copy of the input object?
I think there has been some resolution on that but it has not been
implemented yet. I have not read all of the thread. Can others give a
summary of the following thread please?
I mean, as Ali says OK profiling is
preferable to find out where exactly passing by value is more
efficient than passing by const ref, but I'd like to know anyway what
is the syntax available for this
That syntax is not available yet. Currently you can take by 'ref const'
that does not accept rvalues, or take by value that works for both.

Of course there is also function overloading:

import std.stdio;

struct S
{
int[100] big;
}

void foo(ref const(S) s)
{
writeln("lvalue arg passed by ref");
}

void foo(S s)
{
writeln("either lvalue arg has been copied or rvalue arg has been
moved");
}

void main()
{
auto s = S();

foo(s);
foo(S());
}

However, even the lvalue goes to the by-copy overload of foo:

either lvalue arg has been copied or rvalue arg has been moved
either lvalue arg has been copied or rvalue arg has been moved

Is that by-design? The lvalue s goes to the by-ref overload only if
the variable is const to begin with:

auto s = const(S)();

Now the output is this:

lvalue arg passed by ref
either lvalue arg has been copied or rvalue arg has been moved

Is that how the overload resolution rules work? Have I asked this
already? Why don't I remember? :)

Maybe the programmer should not have such large structs anyway. It is
always an option to use a slice instead of that fixed-length array.
http://bartoszmilewski.com/2008/11/03/who-ordered-rvalue-references-part-3/
but perhaps due to my ignorance I don't get 100% what he is tryring to
say.
That article is about how moving objects is a fundamental operation of
D. C++ has three popular basic operations: construction, copy, and
assignment. C++11 brings another one: move. Move has been a fundamental
struct operation in D for a long time now.
Is it really relevant for what I am asking, like avoiding a
copy-constructor in the input of a function which will not modify its
input, even if I pass by value?
No, not directly relevant but structs object are supposed to be values
without identities. The compiler can move them around freely. Maybe that
is a better way of looking at the problem.

For example, there is not a concept of a copy constructor. The compiler
bit-copies and then provides a chance to make corrections. That is
different from C++.
unfinished language features (scope?)
'scope' is not essential at all, but you are right: There may be other
issues down the road.
I think I may stick with C++ at least for the present project and
maybe use
D for future ones. Advice?
I will let you and others answer that question.

Ali
Shriramana Sharma
2013-05-31 09:42:02 UTC
Permalink
Post by Ali Çehreli
struct Pair {
double x, y;
}
void main()
{
auto p0 = Pair();
auto p1 = Pair(1.5); // only p1.y is 0
Wouldn't p1.y actually be NaN and not 0?

--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरम�
bearophile
2013-05-31 10:04:42 UTC
Permalink
Post by Shriramana Sharma
Wouldn't p1.y actually be NaN and not 0?
Right. You can verify it with a writeln.

Bye,
bearophile
Ali Çehreli
2013-05-31 15:58:25 UTC
Permalink
Post by Shriramana Sharma
Post by Ali Çehreli
Post by Shriramana Sharma
double x,y ;
this () {}
That is not allowed. In D, every type has the .init property, which
is its
Post by Shriramana Sharma
Post by Ali Çehreli
default value.
pair.d(14): Error: constructor pair.pair.this default constructor for
For structs, the default value of an object is required to be known at
compile time, which is its default value. To enforce that rule, the
default constructor cannot be provided for structs.

What the error message is saying is that you can declare it just to
disable its use:

struct Pair {
double x = 10.75;
double y = 20.25;

// Default constructor disabled
@disable this();

// Users must use another constructor:
this(double x, double y) { this.x = x; this.y = y; }
}

As you see, you must also provide a proper constructor that is
appropriate for that type.
Post by Shriramana Sharma
Post by Ali Çehreli
Post by Shriramana Sharma
double abs2 ( pair a ) { return a.x * a.x + a.y * a.y ; }
In C++ the function signature would be: double abs2 ( const pair & a )
So I thought const ref pair a would be appropriate in D -- is that right?
double abs2(ref const(Pair) a) { /* ... */ }
But is this the idiomatic D way of doing things? I mean, would one
normally prefer in or ref const(T) which somehow seems more awkward
than even const T &?
Although 'in' feels like it would make all the sense for an in-parameter
(at least because it conveys our intent to the compiler), Jonathan
explained why 'in' is disappointing.

I still use 'in' in the examples in many chapters, which needs to be
corrected at some point:

http://ddili.org/ders/d.en/function_parameters.html

I gave a talk at DConf 2013, which included D's move semantics (starting
on slide 11):

http://dconf.org/talks/cehreli.html

Interestingly, that talk is already old :) due to the improvements in
dmd 2.063. Here is an adaptation of an example on the dmd 2.063 changelog:

import std.stdio;

struct S
{
this(int i) { writeln("1"); }
this(int i) const { writeln("2"); }
this(int i) immutable { writeln("3"); }
this(int i) shared { writeln("4"); }
}

void main()
{
auto a = new S; // writes "1"
auto b = new const S; // writes "2"
auto c = new immutable S; // writes "3"
auto d = new shared S; // writes "4"
}

D's move semantics are explained in the following blog posts by Bartosz
Milewski:


http://bartoszmilewski.com/2008/10/18/who-ordered-rvalue-references-part-1/


http://bartoszmilewski.com/2008/10/26/who-ordered-rvalue-references-part-2/


http://bartoszmilewski.com/2008/11/03/who-ordered-rvalue-references-part-3/

So, I would use 'ref const' only in special cases and only after
profiling proves that there would be no performance penalty for doing
that. After all, ref is implemented by a pointer and the indirect access
to the members of the object through that pointer may be slow depending
on the application. That indirection may even cause the CPU access
outside of its caches, which is a relatively very slow operation.

However, my short tests on this has demonstrated that by-ref is faster
with today's dmd even on a contrived program that accesses to random
elements of huge arrays.

However, :) if you are going to make a copy of the argument anyway,
always take a struct by-value. That works with both lvalue and rvalue
arguments and in the case of rvalues, you will get the automatic move
semantics.

My choice is by-value for structs unless there is any reason not to.
Also note that by-ref-to-const is an anti-idiom even in C++ even in
surprising places. Everybody defines operator= by taking reference to
const, which may be slower than taking by-value. This is the idiomatic
way of writing operator= in C++:

Foo & operator= (Foo that)
{
this->swap(that);
return *this;
}
Post by Shriramana Sharma
it makes optimization sense to not copy it but just automatically
provide a
Post by Shriramana Sharma
reference to it right?
Unless the language makes a guarantee about that we cannot take the
address of the parameter and save it. We wouldn't know whether it is a
copy or a reference.
Post by Shriramana Sharma
So I would expect in to mean const ref -- doesn't it work that way,
and if
Post by Shriramana Sharma
not, why not?
Another reason is less surprise because structs have copy semantics by
default.

Ali
Shriramana Sharma
2013-05-31 17:49:57 UTC
Permalink
What the error message is saying is that you can declare it just to disable
struct Pair {
double x = 10.75;
double y = 20.25;
// Default constructor disabled
@disable this();
this(double x, double y) { this.x = x; this.y = y; }
}
As you see, you must also provide a proper constructor that is appropriate
for that type.
Sorry but I still don't get it -- if a default constructor is
disallowed for struct-s by the language itself, why should I have to
*tell* the compiler to disable it?
However, :) if you are going to make a copy of the argument anyway, always
take a struct by-value. That works with both lvalue and rvalue arguments and
in the case of rvalues, you will get the automatic move semantics.
My choice is by-value for structs unless there is any reason not to.
So where is the cut-off point? I mean, by-value means a copy is done
every time the function is called right? So how heavy (in terms of
sizeof) would a struct have to be to make passing by ref more
efficient than passing by value?
be slower than taking by-value. This is the idiomatic way of writing
Foo & operator= (Foo that)
{
this->swap(that);
return *this;
}
This assumes that Foo defines a swap method. Maybe good for lists and
such. ... One sec... if you take an argument by value, it means the
copy constructor would be called. So how is this really more efficient
than taking const Foo & as an argument? Is it the code savings that
would be done by avoiding duplicating the copy-constructor code in
operator= too?
it makes optimization sense to not copy it but just automatically provide
a
reference to it right?
Unless the language makes a guarantee about that we cannot take the address
of the parameter and save it. We wouldn't know whether it is a copy or a
reference.
Can you explain? It's not very clear to me.
Another reason is less surprise because structs have copy semantics by
default.
Makes me start thinking I should use class rather than struct for my
pair even though there is no polymorphism etc required. Then I can
just say abs2(const pair). It would automatically be a reference.
Would that be inadvisable for any reason? My program would use pairs
heavily. Would the heap allocation/deallocation/GC be a burden if I
made the pair a class?
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Jonathan M Davis
2013-05-31 19:43:04 UTC
Permalink
Post by Shriramana Sharma
However, :) if you are going to make a copy of the argument anyway, always
take a struct by-value. That works with both lvalue and rvalue arguments
and in the case of rvalues, you will get the automatic move semantics.
My choice is by-value for structs unless there is any reason not to.
So where is the cut-off point? I mean, by-value means a copy is done
every time the function is called right?
The compiler will move an object rather than copy it when it can. So, for
instance, if you pass the function a temporary, it'll move that temporary
rather than copying it. However, if it's called with an lvalue, odds are that
it's going to have to make a copy (though it might be moved if it were the
last time in the caller that the variable was referenced).
Post by Shriramana Sharma
So how heavy (in terms of
sizeof) would a struct have to be to make passing by ref more
efficient than passing by value?
That would depend on the compiler. You'd have to do tests to see. Certainly,
for anything that's small enough to fit in a register, it's likely to be faster
to pass by value. But how much beyond that the struct has to grow before it's
large enough that passing by reference would be cheaper, I don't know. A lot
that the compiler does could affect that. The only way to know for sure is to
test it.

- Jonathan M Davis
Shriramana Sharma
2013-05-31 23:09:07 UTC
Permalink
Post by Jonathan M Davis
Certainly,
for anything that's small enough to fit in a register, it's likely to be faster
to pass by value.
So pardon my ignorance, but a pair which only contains two doubles --
it would fit in a CPU register? How about a bezier class/struct which
contains 4 pairs i.e. 8 doubles?

And that would mean that passing by value is faster 'cause it's
cheaper to copy the data to a register and have the processor operate
on it directly than to copy the pointer (which lies beneath the
reference) and have the indirection and *then* copy (?) the data? (I
mean I'm not sure how these things work -- even when you are using a
pointer/reference, copying from the memory to CPU register is
unavoidable right? C++'s const T & only makes it so that copying from
memory-to-memory can be avoided, right?
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Jonathan M Davis
2013-06-01 01:15:20 UTC
Permalink
Post by Shriramana Sharma
Post by Jonathan M Davis
Certainly,
for anything that's small enough to fit in a register, it's likely to be
faster to pass by value.
So pardon my ignorance, but a pair which only contains two doubles --
it would fit in a CPU register? How about a bezier class/struct which
contains 4 pairs i.e. 8 doubles?
I really don't know. I don't generally deal with anything at the register
level. Normally, what you do is you just pass by value unless you know that
the struct is huge, and then you just worry about whether passing by value or
by reference if profiling makes it clear that passing by value is a performance
hit. But it seems to me that in general, worrying about whether something with
a few ints or floats in it is going to be able to be passed in a register or
not isn't particularly productive unless you've already found it to be a
performance problem or know that you're in an environment where it really
matters (in which case, you probably know enough about registers to be far
better informed about what is and isn't efficient).
Post by Shriramana Sharma
And that would mean that passing by value is faster 'cause it's
cheaper to copy the data to a register and have the processor operate
on it directly than to copy the pointer (which lies beneath the
reference) and have the indirection and *then* copy (?) the data? (I
mean I'm not sure how these things work -- even when you are using a
pointer/reference, copying from the memory to CPU register is
unavoidable right? C++'s const T & only makes it so that copying from
memory-to-memory can be avoided, right?
Why would you be copying the data if it was a pointer that was passed? If you
pass by ref or by pointer, then only a pointer to the data is copied. Member
variables may be copid into registers when operated on, but only the pieces
that you operated on would end up being copied into registers, and only then
if what you're doing requires a register. There's plenty of stuff that just
gets passed around on the stack without any operations being done on them
besides copying or moving them. It all depends on what's being done with them.

But it's been too long since I studied assembly for me to be able to go into
all of the details with registers and the like.

- Jonathan M Davis
Shriramana Sharma via Digitalmars-d-learn
2014-10-22 12:05:35 UTC
Permalink
Hello people. I'm once more looking at D since I participated here a
bit last year. Since I'm still not 100% sure about committing myself
to using D i.o. C++ for my work, I'd really like to resurrect this
thread to clear my lingering doubts (the full thread is at
Post by Jonathan M Davis
The compiler will move an object rather than copy it when it can. So, for
instance, if you pass the function a temporary, it'll move that temporary
rather than copying it. However, if it's called with an lvalue, odds are that
it's going to have to make a copy (though it might be moved if it were the
last time in the caller that the variable was referenced).
I read the Bartosz Milewski article that someone (Ali?) recommended
once more. What I understood is that D avoids the copy-*constructor*
when given an rvalue as a function parameter. But the article seems to
indicate that D will still make a *blit* copy.

However, Jonathan's comment above indicates otherwise (or am I
misreading?). What is actually meant above by "move an object rather
than copy"? In C++(11), move semantics seem to mostly involve a swap
of pointers ponting to the data. Here we are talking about struct
objects directly containing the data.

Does "move" means that the content is going to be "moved" to a new
location in memory i.e. copied and then the old memory freed?

What I'd really like to see D do (please tell me if it does that
already) is to avoid constructing the struct twice, once at the caller
site and second time at the callee site when I'm passing an rvalue
struct as argument. For example:

bezier.di:
struct Bezier { int p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y ; }
void draw(Bezier b) ;
--
test.d:
void main () {
draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200)) ;
}

should only ever cause the one Bezier struct object to be constructed
because the rvalue is not used at the caller site. Does D do that
already?

And even if what I'm passing to the draw function is an lvalue, say a
Bezier b which I have declared in the previous line to draw under
main(), if it is passed in with "in" keyword which implies "const",
then the function is not going to change the value anyway, so there
isn't any need to make a copy and so it makes sense if D actually
directly uses the content of the variable declared under main(). Does
D do this already too?

A further thought: If instead of draw(Bezier), I have Bezier.draw(),
then the values will be taken from the Bezier object directly whether
it is an lvalue or rvalue at the calling site, right?

And extending this, given that UFCS exists in D, draw(Bezier) and
Bezier.draw() should be equivalent, meaning that if Bezier.draw()
doesn't make a copy (even a blit) then draw(Bezier) shouldn't
[assuming of course that it doesn't alter the contents of the object],
right?

Sorry if some of my statements above sound presumptuous, but I'm
really really excited about D, and really really want to get away from
C++, so please bear with me... Thanks!
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
Jonathan M Davis via Digitalmars-d-learn
2014-10-22 19:44:47 UTC
Permalink
On Wednesday, 22 October 2014 at 19:13:58 UTC, Shriramana Sharma
Post by Shriramana Sharma via Digitalmars-d-learn
Hello people. I'm once more looking at D since I participated
here a
bit last year. Since I'm still not 100% sure about committing
myself
to using D i.o. C++ for my work, I'd really like to resurrect
this
thread to clear my lingering doubts (the full thread is at
Post by Jonathan M Davis
The compiler will move an object rather than copy it when it
can. So, for
instance, if you pass the function a temporary, it'll move
that temporary
rather than copying it. However, if it's called with an
lvalue, odds are
that
it's going to have to make a copy (though it might be moved if it were the
last time in the caller that the variable was referenced).
I read the Bartosz Milewski article that someone (Ali?)
recommended
once more. What I understood is that D avoids the
copy-*constructor*
when given an rvalue as a function parameter. But the article
seems to
indicate that D will still make a *blit* copy.
However, Jonathan's comment above indicates otherwise (or am I
misreading?). What is actually meant above by "move an object
rather
than copy"? In C++(11), move semantics seem to mostly involve a
swap
of pointers ponting to the data. Here we are talking about
struct
objects directly containing the data.
Does "move" means that the content is going to be "moved" to a
new
location in memory i.e. copied and then the old memory freed?
Moved means that the memory for the object is blitted from one
location to another, so no copy is made and no destructors or
constructors are run. There's never any freeing of memory
involved, because it involves objects on the stack, not the heap.
The memory that held the object will get reused at somepoint when
the stack pointer moves to the appropriate point in the stack for
that to make sense, but there's no allocation or deallocation
going on regardless.
Post by Shriramana Sharma via Digitalmars-d-learn
What I'd really like to see D do (please tell me if it does that
already) is to avoid constructing the struct twice, once at the
caller
site and second time at the callee site when I'm passing an
rvalue
struct Bezier { int p1x, p1y, c1x, c1y, c2x, c2y, p2x, p2y ; }
void draw(Bezier b) ;
--
void main () {
draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200)) ;
}
should only ever cause the one Bezier struct object to be
constructed
because the rvalue is not used at the caller site. Does D do
that
already?
That will result in a move operation. No copying will take place.
And it technically, it may not actually move anything it all and
just use the object where it's initially constructed. I'm not
sure what the actual, generated machine code ends up doing in
that regard. But there's no copying or double-construction.
Post by Shriramana Sharma via Digitalmars-d-learn
And even if what I'm passing to the draw function is an lvalue,
say a
Bezier b which I have declared in the previous line to draw
under
main(), if it is passed in with "in" keyword which implies
"const",
then the function is not going to change the value anyway, so
there
isn't any need to make a copy and so it makes sense if D
actually
directly uses the content of the variable declared under
main(). Does
D do this already too?
const does _not_ mean that a copy doesn't need to be made. It has
zero effect on that. In fact, if you're passing an lvaue to a
function that doesn't take its arguments by ref, it's almost a
guarantee that a copy will be made. The only exception would be
if the compiler determines that the lvalue in question is never
used after that call, in which case, it might just move the
object rather than copy it.
Post by Shriramana Sharma via Digitalmars-d-learn
A further thought: If instead of draw(Bezier), I have
Bezier.draw(),
then the values will be taken from the Bezier object directly
whether
it is an lvalue or rvalue at the calling site, right?
So, you mean if draw was a member function of Bezier, and you did
something like

Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()

you want to know whether a copy of the Bezier object would be
made? The this member of a struct is a ref - in this case ref
Bezier - so it doesn't make a copy. The object would be
constructed in place and then be destroyed after it's used
(though exactly when it would be destroyed if the object were
created and called in a complex expression, I'm not sure -
possibly as soon as the function call terminated, possibly when
the statement completed).
Post by Shriramana Sharma via Digitalmars-d-learn
And extending this, given that UFCS exists in D, draw(Bezier)
and
Bezier.draw() should be equivalent, meaning that if
Bezier.draw()
doesn't make a copy (even a blit) then draw(Bezier) shouldn't
[assuming of course that it doesn't alter the contents of the
object],
right?
If draw is a free function, then

Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()

is literally transformed into

draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200))

by the compiler, so the behavior of those two lines is identical.
And as I said above, that means that a move is made. Because it's
dealing with a temporary, the compiler knows that the value is
unique and that it can therefore do a move instead of a copy, so
it will.
Post by Shriramana Sharma via Digitalmars-d-learn
Sorry if some of my statements above sound presumptuous, but I'm
really really excited about D, and really really want to get
away from
C++, so please bear with me... Thanks!
I'm not sure why it would sound presumptious if you're asking
whether you're understanding or guesses about how D works are
wrong. Questions are certainly welcome. We all want to see D
succeed.

Here's a related question on SO that might help you as well:

http://stackoverflow.com/questions/6884996/questions-about-postblit-and-move-semantics/6886520#6886520

- Jonathan M Davis
Shriramana Sharma via Digitalmars-d-learn
2014-10-23 05:17:05 UTC
Permalink
Hi Jonathan and thanks again for your kind replies.

On 10/23/14, Jonathan M Davis via Digitalmars-d-learn
Post by Jonathan M Davis via Digitalmars-d-learn
That will result in a move operation. No copying will take place.
And it technically, it may not actually move anything it all and
just use the object where it's initially constructed. I'm not
sure what the actual, generated machine code ends up doing in
that regard. But there's no copying or double-construction.
Well blitting is copying isn't it? I read your SE answer where you
define a move as a blit without the postblit. Hmm. Somehow the name
"move" seems to be misleading...

To clarify that, is this the same as the C++11 move behaviour or is it
subtly different? IIUC in case of a C++ vector, a move would mean that
the length and possibly other direct members of the class including
the pointer to the heap-allocated managed data would be copied to the
target (same as blit I guess) and the managed data itself is not
dupped (i.e. postblit is not called) and we call it a move, yeah? But
it's actually better called a shallow copy, no?

Or is a shallow copy different from a move in any other way?
Post by Jonathan M Davis via Digitalmars-d-learn
const does _not_ mean that a copy doesn't need to be made. It has
zero effect on that. In fact, if you're passing an lvaue to a
function that doesn't take its arguments by ref, it's almost a
guarantee that a copy will be made. The only exception would be
if the compiler determines that the lvalue in question is never
used after that call, in which case, it might just move the
object rather than copy it.
But that doesn't seem logical. If the compiler is able to determine
that the lvalue in question is not modified in the function at all
(either heuristically or by the programmer defining it as "in" or
"const") then it should be able to optimize by not even making the
move aka shallow copy, no?

Of course I understand it doesn't *have* to do an optimization, and
that D believes that ideally optimization opportunities should be
recognized and acted upon by the compiler and the user should not need
to specify places where this is possible (I listened to the
Andrei/Walter panel talk where IIUC they were saying this is the ideal
behaviour and keywords like "pure" "nothrow" etc shouldn't ideally be
needed), but I just want to be sure whether I'm understanding right
that there is indeed the possibility in the present case.
Post by Jonathan M Davis via Digitalmars-d-learn
So, you mean if draw was a member function of Bezier, and you did
something like
Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
you want to know whether a copy of the Bezier object would be
made? The this member of a struct is a ref - in this case ref
Bezier - so it doesn't make a copy.
Actually it doesn't even make a move aka shallow copy right? If that's
right, then sorta here we are seeing how a ref to a temporary can
indeed exist, i.e. implicitly within the member functions. (I'll post
separately on that topic.)
Post by Jonathan M Davis via Digitalmars-d-learn
If draw is a free function, then
Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
is literally transformed into
draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200))
by the compiler, so the behavior of those two lines is identical.
And as I said above, that means that a move is made.
OK so if I read this right, if the function is defined as a free
function, then calling it on a temporary will make a shallow copy (and
I can't use an explicit ref on the temporary argument), but if it is
defined as a member then calling it on a temporary will not even make
a shallow copy. Because of UFCS, how I'm *calling* the function will
not have an effect on whether the shallow copy is made or not, but how
I *defined* it will. Correct?
Post by Jonathan M Davis via Digitalmars-d-learn
I'm not sure why it would sound presumptious if you're asking
whether you're understanding or guesses about how D works are
wrong. Questions are certainly welcome.
That's very nice of you. I only added the advance apology because
sometimes some friends of mine have commented that I come across high
and mighty in saying "this should be like this and this and not like
that" in technical fora where people more knowledgeable than me exist.
As Andrei said in his opening address, it's nice to see that one big
strength of D is the patient and friendly replies of the D community.
:-)
--
Shriramana Sharma ஶ்ரீரமணஶர்மா श्रीरमणशर्मा
via Digitalmars-d-learn
2014-10-23 14:19:44 UTC
Permalink
On Wednesday, 22 October 2014 at 19:44:49 UTC, Jonathan M Davis
The only exception would be if the compiler determines that the
lvalue in question is never used after that call, in which
case, it might just move the object rather than copy it.
There's an additional restriction: local variables are guaranteed
to live until the end of the scope they are declared in, and they
will be destroyed in inverse order of declaration. A move can
only happen if this guarantee wouldn't be violated. This is the
case, for example, if the object doesn't have a destructor. If it
does have one, it can still be moved if the call is at the end of
the variable's scope, and the relative order of destruction of
the other variables is unchanged. (The variable would then be
destroyed on return inside the function, in an order depending on
its position in the argument list.)
So, you mean if draw was a member function of Bezier, and you
did something like
Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
you want to know whether a copy of the Bezier object would be
made? The this member of a struct is a ref - in this case ref
Bezier - so it doesn't make a copy. The object would be
constructed in place and then be destroyed after it's used
(though exactly when it would be destroyed if the object were
created and called in a complex expression, I'm not sure -
possibly as soon as the function call terminated, possibly when
the statement completed).
AFAIK, always at the end of the entire statement, but I don't
know about the order.
via Digitalmars-d-learn
2014-10-23 15:16:15 UTC
Permalink
On Thursday, 23 October 2014 at 05:17:14 UTC, Shriramana Sharma
Post by Shriramana Sharma via Digitalmars-d-learn
Hi Jonathan and thanks again for your kind replies.
On 10/23/14, Jonathan M Davis via Digitalmars-d-learn
Post by Jonathan M Davis via Digitalmars-d-learn
That will result in a move operation. No copying will take
place.
And it technically, it may not actually move anything it all
and
just use the object where it's initially constructed. I'm not
sure what the actual, generated machine code ends up doing in
that regard. But there's no copying or double-construction.
Well blitting is copying isn't it? I read your SE answer where
you
define a move as a blit without the postblit. Hmm. Somehow the
name
"move" seems to be misleading...
I think a clarification of the terms is in order.

* Blitting. This is a low-level bitwise copy from one location to
another. Depending on the circumstances, it may be between two
memory locations, but it can also be from one or more registers
into memory, or vice versa, or between registers. Which one it is
can not be influenced nor predicted reliable from a D program,
but the compiler's optimizer will try to choose the best method
(usually registers).

* Copying. This is a high-level operation, defined by the
programming language specification. For D, it means _blitting_,
followed optionally by a call to a postblit method `this(this)`
that can be used to make adjustments to the new copy (e.g.
duplicating internal buffers, incrementing a reference counter,
...). For C++, both parts are combined in the copy constructor.

* Moving. This is again a high-level concept, meaning that an
object is transferred to a new location (variable, parameter),
and the old location will be invalid afterwards. Semantically, it
should always be equivalent to _copying_ the source to the
destination, and then destroying the source. It is implemented as
_blitting_ from the source to the destination. In D, it is thus
equivalent to copying if there is no postblit defined.

On a language level, D itself has no concept of moving, except
that the specification forbids structs to contain references into
themselves, in order to allow the compiler to choose moving over
copying. But there is no way for the programmer to force it; the
compiler decides at its own discretion.
Post by Shriramana Sharma via Digitalmars-d-learn
To clarify that, is this the same as the C++11 move behaviour
or is it
subtly different? IIUC in case of a C++ vector, a move would
mean that
the length and possibly other direct members of the class
including
the pointer to the heap-allocated managed data would be copied
to the
target (same as blit I guess) and the managed data itself is not
dupped (i.e. postblit is not called) and we call it a move,
yeah?
Yes.
Post by Shriramana Sharma via Digitalmars-d-learn
But
it's actually better called a shallow copy, no?
Or is a shallow copy different from a move in any other way?
Using the above terms, it is a shallow _blit_, because there's no
copy-constructor or postblit being called. The point is that the
compiler only does it if its outcome is semantically equivalent
to a (deep) copy immediately followed by a destruction. This is
commonly the case when the source cannot be accessed after the
move.

To take std::vector as an example, doing a shallow copy if the
source is still accessible after the copy will result in a
semantically different behaviour, because both containers will
now point to the same contents.
Post by Shriramana Sharma via Digitalmars-d-learn
Post by Jonathan M Davis via Digitalmars-d-learn
const does _not_ mean that a copy doesn't need to be made. It
has
zero effect on that. In fact, if you're passing an lvaue to a
function that doesn't take its arguments by ref, it's almost a
guarantee that a copy will be made. The only exception would be
if the compiler determines that the lvalue in question is never
used after that call, in which case, it might just move the
object rather than copy it.
But that doesn't seem logical. If the compiler is able to
determine
that the lvalue in question is not modified in the function at
all
(either heuristically or by the programmer defining it as "in"
or
"const") then it should be able to optimize by not even making
the
move aka shallow copy, no?
In principle, it could, but there is a complication: The function
you call in this particular place can also be called from
somewhere else, potentially from a different library which the
compiler has no control over. Therefore, it needs to provide a
fixed and predictable interface to the outside.

The optimization you have in mind requires pass-by-value to be
turned into pass-by-reference, which would change this interface.
However, the compiler could theoretically provide a different
implementation of the function in addition, and keep the original
one around for when it's needed. But I'd say this is an advanced
optimization technique that you surely cannot rely on.
Post by Shriramana Sharma via Digitalmars-d-learn
Of course I understand it doesn't *have* to do an optimization,
and
that D believes that ideally optimization opportunities should
be
recognized and acted upon by the compiler and the user should
not need
to specify places where this is possible (I listened to the
Andrei/Walter panel talk where IIUC they were saying this is
the ideal
behaviour and keywords like "pure" "nothrow" etc shouldn't
ideally be
needed), but I just want to be sure whether I'm understanding
right
that there is indeed the possibility in the present case.
Post by Jonathan M Davis via Digitalmars-d-learn
So, you mean if draw was a member function of Bezier, and you
did
something like
Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
you want to know whether a copy of the Bezier object would be
made? The this member of a struct is a ref - in this case ref
Bezier - so it doesn't make a copy.
Actually it doesn't even make a move aka shallow copy right? If
that's
right, then sorta here we are seeing how a ref to a temporary
can
indeed exist, i.e. implicitly within the member functions.
(I'll post
separately on that topic.)
Yes, but arguably that's a hole in the language that needs to be
plugged ;-) It's certainly an inconsistency.

The problem is that inside `draw()`, you don't know whether
`this` is a reference to a temporary or a longer lived object.
It's dangerous if you accidentally return a reference to a
temporary (or in many cases, even a stack variable).
Post by Shriramana Sharma via Digitalmars-d-learn
Post by Jonathan M Davis via Digitalmars-d-learn
If draw is a free function, then
Bezier(100, 100, 133, 200, 166, 200, 200, 200).draw()
is literally transformed into
draw(Bezier(100, 100, 133, 200, 166, 200, 200, 200))
by the compiler, so the behavior of those two lines is
identical.
And as I said above, that means that a move is made.
OK so if I read this right, if the function is defined as a free
function, then calling it on a temporary will make a shallow
copy (and
I can't use an explicit ref on the temporary argument), but if
it is
defined as a member then calling it on a temporary will not
even make
a shallow copy. Because of UFCS, how I'm *calling* the function
will
not have an effect on whether the shallow copy is made or not,
but how
I *defined* it will. Correct?
Exactly.

Ali Çehreli
2013-05-31 18:20:14 UTC
Permalink
Post by Shriramana Sharma
Post by Ali Çehreli
struct Pair {
double x = 10.75;
double y = 20.25;
// Default constructor disabled
@disable this();
this(double x, double y) { this.x = x; this.y = y; }
}
As you see, you must also provide a proper constructor that is
appropriate
Post by Shriramana Sharma
Post by Ali Çehreli
for that type.
Sorry but I still don't get it -- if a default constructor is
disallowed for struct-s by the language itself, why should I have to
*tell* the compiler to disable it?
Not when you "have to", when you "want to" disable it.
Post by Shriramana Sharma
Post by Ali Çehreli
However, :) if you are going to make a copy of the argument anyway,
always
Post by Shriramana Sharma
Post by Ali Çehreli
take a struct by-value. That works with both lvalue and rvalue
arguments and
Post by Shriramana Sharma
Post by Ali Çehreli
in the case of rvalues, you will get the automatic move semantics.
My choice is by-value for structs unless there is any reason not to.
So where is the cut-off point? I mean, by-value means a copy is done
every time the function is called right? So how heavy (in terms of
sizeof) would a struct have to be to make passing by ref more
efficient than passing by value?
Nobody knows. The cut-off point would move at any direction by the next
generation of CPUs and systems.
Post by Shriramana Sharma
Post by Ali Çehreli
be slower than taking by-value. This is the idiomatic way of writing
Foo & operator= (Foo that)
{
this->swap(that);
return *this;
}
This assumes that Foo defines a swap method.
This is not a C++ forum, but... :) Yes, it must define a non-throwing
swap member function. That function is almost always extremely fast.
Post by Shriramana Sharma
Maybe good for lists and
such. ... One sec... if you take an argument by value, it means the
copy constructor would be called.
We are talking about assignment, which happens to be copy+destroy. No
problem there.
Post by Shriramana Sharma
So how is this really more efficient
than taking const Foo & as an argument?
It as efficient as an exception-safe operator=. It is not as efficient
as incorrectly written operator=.
Post by Shriramana Sharma
Post by Ali Çehreli
Post by Shriramana Sharma
it makes optimization sense to not copy it but just automatically
provide
Post by Shriramana Sharma
Post by Ali Çehreli
Post by Shriramana Sharma
a
reference to it right?
Unless the language makes a guarantee about that we cannot take the
address
Post by Shriramana Sharma
Post by Ali Çehreli
of the parameter and save it. We wouldn't know whether it is a copy or a
reference.
Can you explain? It's not very clear to me.
I thought your proposal was "compiler should automatically provide a
reference when it makes sense to."

I meant: Assume that you wrote such a function where the compiler can
play such a trick. You would not be sure whether you can take the
address of the parameter and store it for later use, even inside the
function, even to print a diagnostic message. The reason is, you
wouldn't know whether the parameter was a reference to the argument or a
copy of it.
Post by Shriramana Sharma
Post by Ali Çehreli
Another reason is less surprise because structs have copy semantics by
default.
Makes me start thinking I should use class rather than struct for my
pair even though there is no polymorphism etc required. Then I can
just say abs2(const pair). It would automatically be a reference.
Would that be inadvisable for any reason? My program would use pairs
heavily. Would the heap allocation/deallocation/GC be a burden if I
made the pair a class?
Sounds like you really have to run some test code to figure out what
will work best for you. But in theory, no, your pair is not a class. It
is a simple value type.

If you will have member functions, classes will be slower unless you
mark those member function as 'final'. Also, class variables always
bring an indirection because they are reference types.

Ali
Jesse Phillips
2013-05-31 21:25:02 UTC
Permalink
Post by Shriramana Sharma
Sorry but I still don't get it -- if a default constructor is
disallowed for struct-s by the language itself, why should I
have to
*tell* the compiler to disable it?
The compiler always provides a default constructor (maybe not
technically) which is used for .init.

By requesting the default constructor be disabled, is really
saying don't let anyone declare the struct without initializing
it.

Testing shows that in 2.060 the init property is still usable,
which I expected as it is most useful in generic code. But I
don't know if that is sticking around, it is kind of like int a =
void;
Jonathan M Davis
2013-05-31 22:51:10 UTC
Permalink
Post by Jesse Phillips
Post by Shriramana Sharma
Sorry but I still don't get it -- if a default constructor is
disallowed for struct-s by the language itself, why should I
have to
*tell* the compiler to disable it?
The compiler always provides a default constructor (maybe not
technically) which is used for .init.
By requesting the default constructor be disabled, is really
saying don't let anyone declare the struct without initializing
it.
Testing shows that in 2.060 the init property is still usable,
which I expected as it is most useful in generic code. But I
don't know if that is sticking around, it is kind of like int a =
void;
I believe that a disabled init property is still useable in some type
inference stuff (e.g. typeof(type.init) may still work), but if init has been
disabled, then you can't use that struct in any situation which requires
default initialization. It's not like you get = void in the cases where init
is disabled and you need init. Rather, you just can't use that type in that
situation.

- Jonathan M Davis
Loading...