August 17, 2012
On Friday, August 17, 2012 02:32:01 Mehrdad wrote:
> On Friday, 17 August 2012 at 00:10:52 UTC, Jonathan M Davis wrote:
> > In C++, if you have
> > 
> > const vector<T*>& getStuff() const;
> > 
> > which returns a member variable, then as long as const isn't cast away, you know that the container itself won't have any elements added or removed from it, but you have _zero_ guarantees about the elements themselves.
> 
> Right.
> 
> > In contrast, in D,
> > 
> > const ref Array!(T*) getStuff() const;
> > 
> > you would _know_ that not only is the container not altered,
> > but you know that the elements aren't altered either -
> > or anything which the elements point to.
> 
> I'm not so sure about that.
> 
> int yourGlobalCounter;
> 
> struct MyIntPtrArray
> {
> int*[] items;
> 
> MyIntPtrArray()
> {
> items = new int*[1];
> items[0] = &yourGlobalField;
> }
> 
> ref const(int*[]) getItems() const
> {
> ++yourGlobalCounter;
> return items;
> }
> }

What I meant is that you know that nothing was altered through the reference that the getter returned. You don't have any such guarantee in C++.

But even if both the constructor and getItems are pure, you could still pass the initial value of items in as an argument to the constructor making it so that there are potentially references to it outside MyIntPtrArray, which is why immutable buys you so much.

So, the optimizations permitted by const alone (and to some extent even with pure) do tend to be fairly localized precisely because of the number of ways that the const variable could refer to data which is altered by other, mutable references to it.

- Jonathan M Davis
August 17, 2012
On Friday, August 17, 2012 03:59:01 Chris Cain wrote:
> If you're absolutely 100% completely totally certain that the data is mutable (i.e., you have confirmed either through good, sound reasoning OR you have some method of seeing exactly where it is stored in your RAM and you've checked that the place it is stored is writable memory), then __technically__, yes you can cast away and modify away. Apparently, according to some, it's necessary for low level programming. I'd highly discourage this type of behavior because if you're doing something like that I'm nearly certain you could come up with a better design, not to mention you're missing the point of having something const in the first place.

It's still technically undefined behavior. It's just that there's pretty much no way that the compiler is going to be written in a way that it won't work. However, you _do_ still risk running into problems because the compiler may make optimizations that you're violating by modifying the object. So, even if you're _certain_ that the object is actually mutable and you're _certain_ that nothing will blow up when you cast away const and modify it, you _still_ could get bugs due to compiler optimizations.

- Jonathan M Davis
August 17, 2012
On Friday, 17 August 2012 at 02:09:09 UTC, Mehrdad wrote:
> On Friday, 17 August 2012 at 02:02:56 UTC, Jonathan M Davis wrote:
>> How is it a bug? The variable that you're altering is not part of the object.
>
>
> It doesn't need to be.


Not to say it /can't/ be, of course... a const method can certainly legally modify its own object's members. All you need is some aliasing.

http://forum.dlang.org/thread/gjccnstbihxbrwhwwucs@forum.dlang.org?page=3#post-mailman.1286.1345168975.31962.digitalmars-d:40puremagic.com
August 17, 2012
On Friday, August 17, 2012 04:12:10 Mehrdad wrote:
> On Friday, 17 August 2012 at 02:02:51 UTC, Jonathan M Davis wrote:
> > Because there are plenty of functions which take mutable objects but don't actually alter them - particularly when interacting with C code.
> 
> Ah, so that explains that, thanks.
> 
> So to clarify, modifying a mutable object through casting away a const reference is undefined in D, but valid in C++.
> 
> Now the only question is what guarantees that actually gives you that the compiler can use for optimization (aliasing issue above^).

Yeah. It's more than C++, but it's still pretty limited without pure, and if even with pure, the optimizations can still be pretty limited. Immutable gives a _lot_ more. And while there's definitely optimizations to  be had with const, I suspect that dmd doesn't exploit them very well at this point. Other issues have had far higher priority than eking out every last bit of performance that we can. So, regardless of what D's current performance is, I think that it's pretty much a guarantee that it will improve as D matures.

- Jonathan M Davis
August 17, 2012
On Friday, 17 August 2012 at 02:25:22 UTC, Jonathan M Davis wrote:
> Yeah. It's more than C++, but it's still pretty limited without pure, and if even with pure, the optimizations can still be pretty limited.

Yeah, I'm only worried about the language here, not the implementation.



On Friday, 17 August 2012 at 02:14:22 UTC, Jonathan M Davis wrote:
> What I meant is that you know that nothing was altered through the reference that the getter returned. You don't have any such guarantee in C++.


I'm not following..
It seems to contradict my second example:


struct MyStruct
{
	static int* x;
	int y;
	this() { }
	this(int* z) { x = z; }
	auto getValue() const { ++*x; return this.y; }
}
auto s = MyStruct();
s = MyStruct(&s.y);
s.getValue();  // const, but returns 1
s.getValue();  // const, but returns 2
s.getValue();  // const, but returns 3


So unless you're expecting the compiler to have the implementation for the entire class available in order for it to be able to do any kind of optimization (in which case, it would have to do a whole bunch of inference to figure out the aliasing issues, which would amount to what a C++ could try do just as well), I'm not seeing where the additional guarantees/potential optimizations are.
August 17, 2012
On Friday, 17 August 2012 at 02:01:11 UTC, Mehrdad wrote:
>...snip...
>
> Are you sure?

I've already responded to something that is equivalent to what you just posted. I'm not sure if you're intentionally being obtuse (I'll give you the benefit of the doubt) or if your eyes are glossing over when you get to the important parts of my posts, but in either case I don't feel like repeating myself again. For your benefit, I'm repeating myself as clearly as I can:

Const is basically a view of your data. If you have something that's const, it's like looking through a filter that only allows you to do certain things. It is _not_ a guarantee that it's the only view of your data that exists in the universe. Your code, again, shows that you have two views of your memory. x is a mutable view of your data, and you're mutating it. y is your const view of your data. No matter how hard you try (without casts), you can't mutate y. You _can_ mutate x, which y is viewing. The y view updates accordingly, but you didn't mutate y.

Maybe in a second you'll say "oh, but that's mutating y" and I'll promptly ignore you completely this time because you've yet again missed the point:2

You mutated x, not y, y is a view on the same data that x is so mutating x would logically have y's view updated since const isn't a guarantee that you only have one view of the data and thus blah blah blah ... would be my ad infinitum response.



Let me point this out: It's there to give you guarantees and ways to think about your code and to allow you to use the same code for mutable and immutable data. That's it. There's no magic behind it that you're not getting. The idea is that if you want those particular features in your code and you know you can use them effectively, then you use const. Generally, if you want the same code to work on mutable data and immutable data, then you have to use const. It's a bridge that allows the same code to work with either type. You can use this so that you reduce code duplication (and work for you) or/and reduce the amount of instructions loaded in your CPU (potentially reducing the times when your cache is reloaded) or I'm sure there's other good reasons as well, but that's for you to figure out. It isn't a catch all feature. Not everything needs to be bashed with the const golden hammer of doom without any thought behind why you might want it to be const. There's plenty of times where you don't want to use const because it simply doesn't make sense and it doesn't give any additional meaning to your code. But that's okay, you just don't need to use const when it doesn't do anything for you.


Your main problem seems to be that you're intentionally trying to break const as hard as you can. Obviously, there's nonsensical ways to use const, and I don't think anyone would argue that _any_ feature is completely bulletproof to incompetence. Take classes for instance. Do I really need to give you an example that grossly misuses classes? Does that mean that classes have no purpose? Should I always use structs or parallel arrays because classes can be misused?

Instead of trying to misuse the feature, you ought to be spending your energy trying to use the feature for your benefit.


(I'm going to make this the last post on the matter ... I'll respond to Mr. Davis' concern on the bug in just a minute, but I've made my full effort to explain const to you and I don't see where additional conversation on my part can clarify this any further.)
August 17, 2012
On Friday, 17 August 2012 at 02:02:56 UTC, Jonathan M Davis wrote:
>
> How is it a bug? The variable that you're altering is not part of the object.
> That's part of why having pure with const in so valuable. It prevents stuff
> like what you're doing here.
>
> - Jonathan M Davis

Notice that I'm making an immutable(S) in that example. It should then transitively mean that the data pointed to will be immutable as well. The reason this is a bug is because the constructor is currently set up to have the thing you're working with be mutable while you're still constructing it (otherwise, you couldn't even initialize any of the fields). However, this allows invalid code.

Here's a bit simplified version with some extra output:

int gCount;

struct S {
    int* it;
    this(int i) {
        writeln("--- constructor begin ---");
        gCount = i;
        it = &gCount; // Should be compile error
                      //when constructing immutable(S)
        writeln(typeof(it).stringof, " it = ", *it);
        writeln("^^ Notice, typeof(it) is always mutable!");
        writeln("--- constructor end ---");
    }
    ref const(int*) getItem() const {
        ++gCount;
        return it;
    }
}

import std.stdio;

void main() {
    immutable(S) s = immutable(S)(0);
    auto it = s.getItem();
    writeln(typeof(s.it).stringof, " s.it = ", *s.it);
    writeln(typeof(it).stringof, " it = ", *it);
    writeln(typeof(gCount).stringof, " gCount = ", gCount);
    s.getItem();
    writeln(typeof(s.it).stringof, " s.it = ", *s.it);
    writeln(typeof(it).stringof, " it = ", *it);
    writeln(typeof(gCount).stringof, " gCount = ", gCount);
}



I'm almost sure it's been reported before because I've seen talk about this particular issue in the past.

If you run this with optimizations off and on, it shows off the type of optimizations that can be made when something is immutable. For instance, with optimizations off you get:
...
immutable(int*) s.it = 1
const(int*) it = 1
int gCount = 1
immutable(int*) s.it = 2
const(int*) it = 2
int gCount = 2

and on:
...
immutable(int*) s.it = 1
const(int*) it = 1
int gCount = 1
immutable(int*) s.it = 1
const(int*) it = 2
int gCount = 2

As you can see, the optimization is that it doesn't even check s.it's memory location, it just puts the previous value in place.

But yeah, it's a bug that this code is allowed.
August 17, 2012
On Friday, August 17, 2012 04:30:42 Mehrdad wrote:
> So unless you're expecting the compiler to have the implementation for the entire class available in order for it to be able to do any kind of optimization (in which case, it would have to do a whole bunch of inference to figure out the aliasing issues, which would amount to what a C++ could try do just as well), I'm not seeing where the additional guarantees/potential optimizations are.

It's probably going to be restricted to primitive types in many cases due to not knowing what member functions are doing. But take this code for example:

auto i = new int;
*i = 5;
const c = i;
writeln(c);
func(c); //obviously takes const or it wouldn't compile
writeln(c);

The compiler _knows_ that c is the same before and after the call to func, because it knows that no other references to that data can exist. And since it's a built-in type, it also knows that there's no way that any functions operating on func can wile away any mutable, global references to it. So, if we were doing something more interesting than writeln before and after func - something which could actually be optimized based on the knowledge that c is unchanged - then const alone is enough to guarantee that that optimization is valid. The same cannot be said of C++, which could cast away const on c inside of func and do who-knows-what to it.

If you use a class instead of int and take it one step further and simply make its constructor pure, then the compiler _still_ knows that the object is the same before and after the call to func, even if none of the class' other functions are pure, because it knows that no mutable references to the data can exist beyond i.

Once you're dealing with code which doesn't include the creation of the object, it's likely much harder to make any gurantees about it not being altered, because the compiler doesn't know whether any other references to the data exist or not, and then purity matters that much more, but if the compiler knows that no other references to the data exists, then const is enough, even if the object is passed to other functions, unlike with C++.

- Jonathan M Davis
August 17, 2012
On Friday, 17 August 2012 at 02:33:46 UTC, Chris Cain wrote:
> I've already responded to something that is equivalent to what you just posted. I'm not sure if you're intentionally being obtuse (I'll give you the benefit of the doubt)

Thanks... I promise I'm not >__< I'm just having trouble seeing what guarantees anyone is getting.



> or if your eyes are glossing over when you get to the important parts of my posts, but in either case I don't feel like repeating myself again. For your benefit, I'm repeating myself as clearly as I can:

Ok thanks for trying again. :)


> Const is basically a view of your data.
> If you have something that's const, it's like looking through a filter that only allows you to do certain things. It is _not_ a guarantee that it's the only view of your data that exists in the universe.

Yup, just like in C++.


> Your code, again, shows that you have two views of your memory. x is a mutable view of your data, and you're mutating it.

Yup, so far so good.


> y is your const view of your data. No matter how hard you try (without casts), you can't mutate y.

Yup.


> You _can_ mutate x, which y is viewing. The y view updates accordingly, but you didn't mutate y.

Yup.


> Maybe in a second you'll say "oh, but that's mutating y" and I'll promptly ignore you completely this time because you've yet again missed the point:2

Nope, not what I said. :)


> You mutated x, not y, y is a view on the same data that x is so mutating x would logically have y's view updated since const isn't a guarantee that you only have one view of the data and thus blah blah blah ... would be my ad infinitum response.

Yes, I already understood this, and I'm pretty sure I still do.




What you're saying, I understand.

What is seems to be missing is the answer to my question:

What _guarantees_ can the compiler make about your code, /based/ on the fact that y is a const view, or that the entire struct is const?


You keep on telling me that y is a const view, which, obviously, it is.

But the question is: why is that useful, compared to C++?

e.g. What kind of a guarantee can a D compiler make about code that calls a const method, which a C++ compiler probably wouldn't be able to?



> Let me point this out: It's there to give you guarantees and ways to think about your code and to allow you to use the same code for mutable and immutable data. That's it. There's no magic behind it that you're not getting. The idea is that if you want those particular features in your code and you know you can use them effectively, then you use const. Generally, if you want the same code to work on mutable data and immutable data, then you have to use const. It's a bridge that allows the same code to work with either type. You can use this so that you reduce code duplication (and work for you) or/and reduce the amount of instructions loaded in your CPU (potentially reducing the times when your cache is reloaded) or I'm sure there's other good reasons as well, but that's for you to figure out. It isn't a catch all feature. Not everything needs to be bashed with the const golden hammer of doom without any thought behind why you might want it to be const. There's plenty of times where you don't want to use const because it simply doesn't make sense and it doesn't give any additional meaning to your code. But that's okay, you just don't need to use const when it doesn't do anything for you.
>
>
> Your main problem seems to be that you're intentionally trying to break const as hard as you can.


"Hacking around", I guess you could put it. :-D
As well as trying to clarify things for other people who might have the same questions as well (which I'm sure they would).

> Obviously, there's nonsensical ways to use const, and I don't think anyone would argue that _any_ feature is completely bulletproof to incompetence. Take classes for instance. Do I really need to give you an example that grossly misuses classes? Does that mean that classes have no purpose? Should I always use structs or parallel arrays because classes can be misused?
> Instead of trying to misuse the feature, you ought to be spending your energy trying to use the feature for your benefit.


It's not a question about misuse, it's a question about guarantees. The compiler simply _isn't_ allowed to do something that works for 99% of code, while breaking that 1% that misuses it (but is legal).

i.e. It's a code generation issue, not a misuse issue.



> (I'm going to make this the last post on the matter ... I'll respond to Mr. Davis' concern on the bug in just a minute, but I've made my full effort to explain const to you and I don't see where additional conversation on my part can clarify this any further.)



Well ok, nice speaking with you then. :)
August 17, 2012
On Friday, 17 August 2012 at 02:49:45 UTC, Jonathan M Davis wrote:
> But take this code for example:
>
> auto i = new int;
> *i = 5;
> const c = i;
> writeln(c);
> func(c); //obviously takes const or it wouldn't compile
> writeln(c);
>
> The compiler _knows_ that c is the same before and after the call to func, because it knows that no other references to that data can exist.


Is there any reason why your example didn't just say

> const(int*) c = null;
> writeln(c);
> func(c);
> writeln(c);


i.e. What was the point of 'i' there?
And why can't a C++ compiler do the same thing?
'c' is a const object, so if C++ code was to modify it, it would be undefined behavior, just like in D.
Sorry, I'm a little confused at what you were illustrating here.