www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - IFTI, value types, and top-level const

reply "Peter Alexander" <peter.alexander.au gmail.com> writes:
This code fails to compile because T is deduced to be `const int`:

void foo(T)(T x)
{
     x++;
}

void main()
{
     const int y = 0;
     foo(y);
}


Analogous code in C++ works because C++ strips the top-level 
const.

Is there any reason this isn't done in D with non-ref value type 
parameters? Of course, I can use Unqual, but it is a hassle to 
always remember. It also doesn't help with bloat because these 
will all call different functions:

const int a;
immutable int b;
int c;
foo(a);
foo(b);
foo(c);

What is the value in things being the way they are?
Jun 07 2013
next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 7 June 2013 at 13:58:33 UTC, Peter Alexander wrote:
 This code fails to compile because T is deduced to be `const 
 int`:

 void foo(T)(T x)
 {
     x++;
 }

 void main()
 {
     const int y = 0;
     foo(y);
 }


 Analogous code in C++ works because C++ strips the top-level 
 const.

 Is there any reason this isn't done in D with non-ref value 
 type parameters? Of course, I can use Unqual, but it is a 
 hassle to always remember. It also doesn't help with bloat 
 because these will all call different functions:

 const int a;
 immutable int b;
 int c;
 foo(a);
 foo(b);
 foo(c);

 What is the value in things being the way they are?

I seem to specifically remember that it *didn't* work this way. Isn't this a regression?
Jun 07 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Friday, 7 June 2013 at 16:24:16 UTC, monarch_dodra wrote:
 I seem to specifically remember that it *didn't* work this way. 
 Isn't this a regression?

...and I seem to have been wrong. This has never worked. And I can also confirm that C++ does strip the top level const in this case. I agree, it would be nice for D to do this too.
Jun 07 2013
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Friday, 7 June 2013 at 16:24:16 UTC, monarch_dodra wrote:
 I seem to specifically remember that it *didn't* work this way. 
 Isn't this a regression?

I checked the last 5 versions, and this code has never compiled. (If it were a regression, I would have hoped a unit test would have caught it!)
Jun 07 2013
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Friday, 7 June 2013 at 16:33:50 UTC, monarch_dodra wrote:
 On Friday, 7 June 2013 at 16:24:16 UTC, monarch_dodra wrote:
 I seem to specifically remember that it *didn't* work this 
 way. Isn't this a regression?

...and I seem to have been wrong. This has never worked. And I can also confirm that C++ does strip the top level const in this case. I agree, it would be nice for D to do this too.

Ack, it appears it is only done for arrays and pointers :-S string foo(T)(T x) { return T.stringof; } void main() { class C {} const int[] a; const int* b; const C c; const int d; import std.stdio; writeln(foo(a)); // const(int)[] writeln(foo(b)); // const(int)* writeln(foo(c)); // const(C) writeln(foo(d)); // const(int) } Obviously you can't strip anything from (c), but I have no idea why (d) isn't stripped. This is bizarre. ... or is this because of the const postblit problem?
Jun 07 2013
prev sibling next sibling parent reply "Jonathan M Davis" <jmdavisProg gmx.com> writes:
On Friday, June 07, 2013 18:43:11 Peter Alexander wrote:
 Ack, it appears it is only done for arrays and pointers :-S

And it pretty much only does that because of all of the issues that const and immutable strings were giving us with range-based functions. IFTI now automatically slices arrays, and slices are now tail-const. Now, we certainly can't strip const in the general case, but I'm inclined to agree that when the mutable version of the type has no mutable indirections, stripping const would make sense. However, one problem that I can think of is that if you're dealing with an rvalue and you don't strip the const, under some circumstances, you can move the object and avoid a copy altogether, but if you strip the const, you can't do that or you'd violate the constness of the object (though maybe you could still get away with it if the type had no postblit) - and that's problem that doesn't exist in C++, since C++ would always do the copy. So, I don't know if we can quite get away with stripping const even if the type has no mutable indirections. It's certainly something that we should consider though. - Jonathan M Davis
Jun 07 2013
parent reply Timon Gehr <timon.gehr gmx.ch> writes:
On 06/09/2013 04:31 PM, Diggory wrote:
 On Sunday, 9 June 2013 at 12:19:13 UTC, Peter Alexander wrote:
 On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with
 indirection:

Part of my point here (that you seem to have missed) is that there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can.

A full copy is only required if you go from const->mutable, but that's completely expected and unavoidable. Going from const->const or mutable->mutable will preserve the move opportunity. It's completely a non-issue as far as I'm concerned: if you want a mutable value and you're given a const value then you *must* make a deep copy. This is true in current D. The difference comes in with how we handle IFTI. When I see this: void foo(T)(T x) {} I read this as explicitly requesting a mutable T, and if you call it with a const(T) then yes you will need a copy (it's unavoidable). I would suggest that if you do NOT need a mutable T, then you should specifically ask for const: void foo(T)(const T x) {} That way, you get the move no matter what.

Yeah, as long as it still prefers to match "const" during overload resolution then it's always possible to provide a const version when needed: void foo(T)(T x) {} void foo(T)(const T x) {} const int a = 1; foo(a); Clearly T should match "int" rather than "const(int)", and then the second overload called.

Then clearly DMD is buggy.
Jun 09 2013
parent Timon Gehr <timon.gehr gmx.ch> writes:
On 06/10/2013 12:56 AM, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 16:36:45 Timon Gehr wrote:
 On 06/09/2013 04:31 PM, Diggory wrote:
 Yeah, as long as it still prefers to match "const" during overload
 resolution then it's always possible to provide a const version when
 needed:

 void foo(T)(T x) {}
 void foo(T)(const T x) {}

 const int a = 1;
 foo(a);

 Clearly T should match "int" rather than "const(int)", and then the
 second overload called.

Then clearly DMD is buggy.

It's clearly as in that's the way that he thinks that it should clearly work, not as in that's how it's actually supposed to work. ...

Yes, that's clear.
Jun 09 2013
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Saturday, 8 June 2013 at 02:18:06 UTC, Jonathan M Davis wrote:
 On Friday, June 07, 2013 18:43:11 Peter Alexander wrote:
 Ack, it appears it is only done for arrays and pointers :-S

And it pretty much only does that because of all of the issues that const and immutable strings were giving us with range-based functions. IFTI now automatically slices arrays, and slices are now tail-const. Now, we certainly can't strip const in the general case, but I'm inclined to agree that when the mutable version of the type has no mutable indirections, stripping const would make sense. However, one problem that I can think of is that if you're dealing with an rvalue and you don't strip the const, under some circumstances, you can move the object and avoid a copy altogether, but if you strip the const, you can't do that or you'd violate the constness of the object (though maybe you could still get away with it if the type had no postblit) - and that's problem that doesn't exist in C++, since C++ would always do the copy. So, I don't know if we can quite get away with stripping const even if the type has no mutable indirections. It's certainly something that we should consider though.

It looks like the core issue here is that it's simply not possible to create a mutable copy of a const object with indirection: struct S { int* p; } const(S) a; S b = a; That's fair, because the current way of copying the object (via memcpy+postblit) could lead to type system violations. I believe this is a hole in the language. It means that it's impossible to create slices or pointer-like types in a library since they require this compiler magic to strip the top-level const. This is just because of how struct copy with postblit works, it's certainly possible and type safe in theory with some sort of copy constructor: struct S { int* p; this(const(S) s) { p = new int; *p = *s.p; } } const(S) a; S b = S(a); And a templated slice-like type could be made with tail-const semantics. struct Slice(T) { T* ptr; size_t length; static if (is(T==const)) { this(const Slice!T s) { ptr = s.ptr; length = s.length; } this(const Slice!(Unqual!T) s) { ptr = s.ptr; length = s.length; } } } The question is, can a copy constructor be introduced into the language without breaking lots of code, and preserving D's approach to move operations, i.e. not requiring explicit C++ style rvalue references. I'm not sure.
Jun 09 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with
 indirection:

Part of my point here (that you seem to have missed) is that there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can.
 The question is, can a copy constructor be introduced into the
 language without breaking lots of code, and preserving D's
 approach to move operations, i.e. not requiring explicit C++
 style rvalue references. I'm not sure.

I fully expect that copy constructors could be added, and AFAIK, we're going to have to at some point, because postlbit constructors cannot copy const or immutable objects - even to create another const or immutable object. Postblit constructors simply do not work with const or immutable, because what they're trying to do is fundamentally broken with const or immutable. You must create objects as const or immutable, not copy them and then alter them as postblits do. It's come up before, and Andrei and Walter have discussed it and supposedly have some sort of solution, but they've never explained it publicly, and AFIK, nothing has been done to fix the problem. The other big problem which is related is how to deal with tail-const and ranges. It's currently very difficult right now (if it's even possible), which makes it so that you should pretty much never mix const and ranges. I know that this is one of Steven Schveighoffer's pet peeves, and he's working on some sort of proposal to fix it, but he hasn't finished it yet. Copying const or immutable objects in general tends to be a bit iffy unless they're value types. - Jonathan M Davis
Jun 09 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with
 indirection:

Part of my point here (that you seem to have missed) is that there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can. - Jonathan M Davis

Well, these are primitives we are talking about. I'm not sure passing an int by value instead of by ref is much more expansive. We already do the same for slices/pointers, so really, all we are doing is making things consistent. I agree with your point for structs though. Given any struct, even if POD without indirections, const means const. But for built-in primitives, I don't think it is a problem.
Jun 09 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 I fully expect that copy constructors could be added, and 
 AFAIK, we're going
 to have to at some point, because postlbit constructors cannot 
 copy const or
 immutable objects - even to create another const or immutable 
 object. Postblit
 constructors simply do not work with const or immutable, 
 because what they're
 trying to do is fundamentally broken with const or immutable. 
 You must create
 objects as const or immutable, not copy them and then alter 
 them as postblits
 do. It's come up before, and Andrei and Walter have discussed 
 it and
 supposedly have some sort of solution, but they've never 
 explained it
 publicly, and AFIK, nothing has been done to fix the problem.

 - Jonathan M Davis

I don't really see what sets apart a "copy constructor" from a "postblit contructor": They have a different sequence of operations, but it's not like the copy constructor will magically have the object ready for use without ever mutating anything? So if your CC looks like this: struct S { int i = 0; this(typeof(this) other) { i = other.i; //oops! mutable operation! } } Back to square one, no? Just because postblit means "copy the bits over, and then construct", doesn't mean the compiler has to place the actual bits in the immutable area before the object is completly constructed. Which is how a CC would work around the problem, unless I'm mistaken. postblit should just be able to modify its own members, as if they were tail const (though there might be a problem for class references...). The problem right now (AFAIK), is that for the compiler, postblit is "just" a function, but it should be more than that.
Jun 09 2013
prev sibling next sibling parent "monarch_dodra" <monarchdodra gmail.com> writes:
On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 The other big problem which is related is how to deal with 
 tail-const and
 ranges. It's currently very difficult right now (if it's even 
 possible), which
 makes it so that you should pretty much never mix const and 
 ranges. I know
 that this is one of Steven Schveighoffer's pet peeves, and he's 
 working on some
 sort of proposal to fix it, but he hasn't finished it yet.

 - Jonathan M Davis

My pet peeve is how phobos casts away the constness of ranges all over the place, as if it was a trivial and legal operation.
Jun 09 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 09, 2013 13:03:16 monarch_dodra wrote:
 My pet peeve is how phobos casts away the constness of ranges all
 over the place, as if it was a trivial and legal operation.

It definitely shouldn't do that, and I would argue that any place that it's doing it is a bug. - Jonathan M Davis
Jun 09 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 09, 2013 13:02:09 monarch_dodra wrote:
 On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 I fully expect that copy constructors could be added, and
 AFAIK, we're going
 to have to at some point, because postlbit constructors cannot
 copy const or
 immutable objects - even to create another const or immutable
 object. Postblit
 constructors simply do not work with const or immutable,
 because what they're
 trying to do is fundamentally broken with const or immutable.
 You must create
 objects as const or immutable, not copy them and then alter
 them as postblits
 do. It's come up before, and Andrei and Walter have discussed
 it and
 supposedly have some sort of solution, but they've never
 explained it
 publicly, and AFIK, nothing has been done to fix the problem.
 
 - Jonathan M Davis

I don't really see what sets apart a "copy constructor" from a "postblit contructor": They have a different sequence of operations, but it's not like the copy constructor will magically have the object ready for use without ever mutating anything? So if your CC looks like this: struct S { int i = 0; this(typeof(this) other) { i = other.i; //oops! mutable operation! } } Back to square one, no? Just because postblit means "copy the bits over, and then construct", doesn't mean the compiler has to place the actual bits in the immutable area before the object is completly constructed. Which is how a CC would work around the problem, unless I'm mistaken. postblit should just be able to modify its own members, as if they were tail const (though there might be a problem for class references...). The problem right now (AFAIK), is that for the compiler, postblit is "just" a function, but it should be more than that.

A copy constructor for const or immutable would be in the same boat as any const or immutable constructor. No portion of the object gets mutated. It's constructed as const or immutable, and anything else is an error. But postblit constructors specifically do a bitwise copy and then start mutating the newly constructed struct, and that violates the type system, because it would mean that it's mutating a const or immutable object. And whereas a const or immutable constructor cannot assign a member variable after that member variable has been read, a postblit constructor _has_ to or it can't look at the state of the original object to copy it, because it doesn't have access to the original object and therefore can only look at the current object (which is a bitwise copy of the original). I just don't see how you can make postblit constructors work with const or immutable without violating the type system. - Jonathan M Davis
Jun 09 2013
prev sibling next sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 09, 2013 12:59:33 monarch_dodra wrote:
 On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with

 indirection:

there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can. - Jonathan M Davis

Well, these are primitives we are talking about. I'm not sure passing an int by value instead of by ref is much more expansive. We already do the same for slices/pointers, so really, all we are doing is making things consistent. I agree with your point for structs though. Given any struct, even if POD without indirections, const means const. But for built-in primitives, I don't think it is a problem.

It probably would be fine for primitives, but it's nowhere near as clearcut for structs, and it would arguably make things _less_ consistent if IFTI took the tail-const version of all primitives but not for structs. So, I don't know if it's a good idea or not for IFTI to take the tail-const type for all primitive types. - Jonathan M Davis
Jun 09 2013
prev sibling next sibling parent "Peter Alexander" <peter.alexander.au gmail.com> writes:
On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with
 indirection:

Part of my point here (that you seem to have missed) is that there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can.

A full copy is only required if you go from const->mutable, but that's completely expected and unavoidable. Going from const->const or mutable->mutable will preserve the move opportunity. It's completely a non-issue as far as I'm concerned: if you want a mutable value and you're given a const value then you *must* make a deep copy. This is true in current D. The difference comes in with how we handle IFTI. When I see this: void foo(T)(T x) {} I read this as explicitly requesting a mutable T, and if you call it with a const(T) then yes you will need a copy (it's unavoidable). I would suggest that if you do NOT need a mutable T, then you should specifically ask for const: void foo(T)(const T x) {} That way, you get the move no matter what.
Jun 09 2013
prev sibling next sibling parent "Diggory" <diggsey googlemail.com> writes:
On Sunday, 9 June 2013 at 12:19:13 UTC, Peter Alexander wrote:
 On Sunday, 9 June 2013 at 10:49:17 UTC, Jonathan M Davis wrote:
 On Sunday, June 09, 2013 12:33:39 Peter Alexander wrote:
 It looks like the core issue here is that it's simply not
 possible to create a mutable copy of a const object with
 indirection:

Part of my point here (that you seem to have missed) is that there is a cost to making it so that const is stripped from the type even if you can do so. It _forces_ a copy if the value being passed in isn't mutable, whereas if the constness were the same, then the value being passed in could potentially be moved rather than copied. As such, I don't know if we even want to strip the constness even if we can.

A full copy is only required if you go from const->mutable, but that's completely expected and unavoidable. Going from const->const or mutable->mutable will preserve the move opportunity. It's completely a non-issue as far as I'm concerned: if you want a mutable value and you're given a const value then you *must* make a deep copy. This is true in current D. The difference comes in with how we handle IFTI. When I see this: void foo(T)(T x) {} I read this as explicitly requesting a mutable T, and if you call it with a const(T) then yes you will need a copy (it's unavoidable). I would suggest that if you do NOT need a mutable T, then you should specifically ask for const: void foo(T)(const T x) {} That way, you get the move no matter what.

Yeah, as long as it still prefers to match "const" during overload resolution then it's always possible to provide a const version when needed: void foo(T)(T x) {} void foo(T)(const T x) {} const int a = 1; foo(a); Clearly T should match "int" rather than "const(int)", and then the second overload called.
Jun 09 2013
prev sibling parent Jonathan M Davis <jmdavisProg gmx.com> writes:
On Sunday, June 09, 2013 16:36:45 Timon Gehr wrote:
 On 06/09/2013 04:31 PM, Diggory wrote:
 Yeah, as long as it still prefers to match "const" during overload
 resolution then it's always possible to provide a const version when
 needed:
 
 void foo(T)(T x) {}
 void foo(T)(const T x) {}
 
 const int a = 1;
 foo(a);
 
 Clearly T should match "int" rather than "const(int)", and then the
 second overload called.

Then clearly DMD is buggy.

It's clearly as in that's the way that he thinks that it should clearly work, not as in that's how it's actually supposed to work. IFTI itself _never_ strips constness. The only place where anything close to that happens is with arrays, and what happens there is that IFTI takes the type of the _slice_ of the array, which is tail-const, so that layer of constness is effectively stripped, but IFTI doesn't do it directly. If the slice had been const like the original, then IFTI would have inferred the type as const. It would be a definite design change to make it so that IFTI stripped constness. Whether that change should be made is debatable and most definitely _not_ clear. Maybe it should; maybe it shouldn't. The side effects of such a change would have to be examined in great detail. - Jonathan M Davis
Jun 09 2013