May 08, 2014
Am 08.05.2014 15:57, schrieb monarch_dodra:
> On Thursday, 8 May 2014 at 12:48:17 UTC, Sönke Ludwig wrote:
>> Am 08.05.2014 13:05, schrieb monarch_dodra:
>>> Not necessarily: As soon as indirections come into play, you are
>>> basically screwed, since const is "turtles all the way down".
>>>
>>> So for example, the conversion from "const RefCounted!T" to
>>> "RefCounted!(const T)" is simply not possible, because it strips the
>>> const-ness of the ref count.
>>>
>>> What we would *really* need here is NOT:
>>> "const RefCounted!T" => "RefCounted!(const T)"
>>> But rather
>>> "RefCounted!T" => "RefCounted!(const T)"
>>>
>>> The idea is to cut out the "head const" directly. This also applies to
>>> most ranges too BTW.
>>>
>>> We'd be much better of if we never used `const MyRange!T` to begin with,
>>> but simply had a conversion from `MyRange!T` to `MyRange!(const T)`,
>>> which references the same data.
>>>
>>> In fact, I'm wondering if this might not be a more interesting direction
>>> to explore.
>>
>> The reference count _must_ be handled separate from the payload's
>> const-ness, or a const(RefCount!T) would be completely dysfunctional.
>
> Right, which is my point: "const(RefCount!T)" *is* dysfunctional, which
> is why you'd want to skip it out entirely in the first place.This holds
> true for types implemented with RefCount, such as Array and Array.Range.

Okay, I didn't know that. For various reasons (mostly weak ref support) I'm using my own RefCount template, which casts away const-ness of the reference counter internally.
May 08, 2014
Am 08.05.2014 16:22, schrieb Jonathan M Davis via Digitalmars-d:
> On Thu, 08 May 2014 14:48:18 +0200
> Sönke Ludwig via Digitalmars-d <digitalmars-d@puremagic.com> wrote:
>
>> Am 08.05.2014 13:05, schrieb monarch_dodra:
>>> On Thursday, 8 May 2014 at 07:09:24 UTC, Sönke Ludwig wrote:
>>>> Just a general note: This is not only interesting for range/slice
>>>> types, but for any user defined reference type (e.g. RefCounted!T
>>>> or Isolated!T).
>>>
>>> Not necessarily: As soon as indirections come into play, you are
>>> basically screwed, since const is "turtles all the way down".
>>>
>>> So for example, the conversion from "const RefCounted!T" to
>>> "RefCounted!(const T)" is simply not possible, because it strips the
>>> const-ness of the ref count.
>>>
>>> What we would *really* need here is NOT:
>>> "const RefCounted!T" => "RefCounted!(const T)"
>>> But rather
>>> "RefCounted!T" => "RefCounted!(const T)"
>>>
>>> The idea is to cut out the "head const" directly. This also applies
>>> to most ranges too BTW.
>>>
>>> We'd be much better of if we never used `const MyRange!T` to begin
>>> with, but simply had a conversion from `MyRange!T` to
>>> `MyRange!(const T)`, which references the same data.
>>>
>>> In fact, I'm wondering if this might not be a more interesting
>>> direction to explore.
>>
>> The reference count _must_ be handled separate from the payload's
>> const-ness, or a const(RefCount!T) would be completely dysfunctional.
>
> Unless the reference count is completely separate from const(RefCount!T)
> (which would mean that the functions which accessed the reference couldn't
> pure - otherwise they couldn't access the reference count), const(RefCount!T)
> _is_ completely dysfunctional. The fact that D's const can't be cast away in
> to do mutation without violating the type system means that pretty much
> anything involving references or pointers is dysfunctional with const if you
> ever need to mutate any of it (e.g. for a mutex or a reference count).
> std.typecons.RefCounted is completely broken if you make it const, and I
> would expect that of pretty much any wrapper object intended to do reference
> counting. Being able to do RefCounted!T -> RefCounted!(const T) makes some
> sense, but const(RefCounted!T) pretty much never makes sense.
>
> That being said, unlike monarch_dodra, I think that it's critical that we find
> a way to do the equivalent of const(T[]) -> const(T)[] with ranges - i.e.
> const(Range!T) or const(Range!(const T)) -> Range!(const T). I don't think
> that Range!T -> Range!(const T) will be enough at all. It's not necessarily
> the case that const(Range!T) -> Range!(const T) would always work, but it's
> definitely the case that it would work if the underlying data was in an array,
> and given what it takes for a forward range to work, it might even be the case
> that a forward range could do be made to do that conversion by definition.
>
> The problem is the actual mechanism of converting const(Range!T) to
> Range!(const T) in the first place (due to recursive template instantiations
> and the fact that the compiler doesn't understand that they're related). The
> concept itself is perfectly sound in many cases - unlike with
> const(RefCounted!T) - because in most cases, with a range, it's the data being
> referred to which needs to stay const, whereas the bookkeeping stuff for it
> can be copied and thus be made mutable. With a reference count, however, you
> have to mutate what's actually being pointed to rather than being able to make
> a copy and mutate that. So, const(RefCounted!T) -> RefCounted!(const T) will
> never work - not unless the reference is outside of const(RefCounted!T), in
> which case, it can't be pure, which can be just as bad as not working with
> const.
>
> - Jonathan M Davis
>

Unless I'm completely mistaken, it's safe to cast away const when it is known that the original reference was constructed as mutable. Anyway, this is what I do in my own RefCount struct. But my main point was that any user defined reference type is affected by the head vs. tail const issue, not just range types. So a decent solution should solve it for all of those types.

BTW, since RefCount would usually do manual memory management, it can't be used in pure contexts anyway. Proper support of scope/lent pointers would solve that, though. Ideally, there would be a way to allow a RefCount!T to implicitly cast to T if passed as a scoped parameter.
May 08, 2014
On Thursday, 8 May 2014 at 15:39:24 UTC, Sönke Ludwig wrote:
> Unless I'm completely mistaken, it's safe to cast away const when it is known that the original reference was constructed as mutable.

Depends. If the original type referencing the (originally mutable) allocated data happens to be *im*-mutable, then it *would* be illegal to modify it, even if you *can*.

EG:

immutable myFirstRC = RefCounted!int(1);
immutable myReference = myFirstRC;

In this particular case, you'd be modifying a ref count that can only be accessed via an immutable pointer. As such, the compiler is free to assume the value has never been changed, and avoid reading it all together, destroying your payload at the end of "myFirstRC"'s life-cycle.

I honestly don't think (given D's transitive constness mechanics), that a const RefCount *could* make any sense.

--------

If you have const data referencing mutable data, then yes, you can cast away all the const you want, but at that point, it kind of makes the whole "const" thing moot.
May 08, 2014
On 05/08/2014 06:02 PM, monarch_dodra wrote:
>
> If you have const data referencing mutable data, then yes, you can cast
> away all the const you want, but at that point, it kind of makes the
> whole "const" thing moot.

This is not guaranteed to work. I guess the only related thing that is safe to do is casting away const, but then not modifying the memory.
May 08, 2014
Am 08.05.2014 18:10, schrieb Timon Gehr:
> On 05/08/2014 06:02 PM, monarch_dodra wrote:
>>
>> If you have const data referencing mutable data, then yes, you can cast
>> away all the const you want, but at that point, it kind of makes the
>> whole "const" thing moot.
>
> This is not guaranteed to work. I guess the only related thing that is
> safe to do is casting away const, but then not modifying the memory.

For what practical reason would that be the case? I know that the spec states "undefined behavior", but AFAICS, there is neither an existing, nor a theoretical reason, why this should fail:

int* i = new int;
const(int)* j = i;
int* k = cast(int*)j;
*k = 0;

Anyway this is going pretty much off topic...
May 08, 2014
On Thursday, 8 May 2014 at 16:30:13 UTC, Sönke Ludwig wrote:
> For what practical reason would that be the case? I know that the spec states "undefined behavior", but AFAICS, there is neither an existing, nor a theoretical reason, why this should fail:

Compiler optimizations based on immutability.

David
May 08, 2014
Am 08.05.2014 18:02, schrieb monarch_dodra:
> On Thursday, 8 May 2014 at 15:39:24 UTC, Sönke Ludwig wrote:
>> Unless I'm completely mistaken, it's safe to cast away const when it
>> is known that the original reference was constructed as mutable.
>
> Depends. If the original type referencing the (originally mutable)
> allocated data happens to be *im*-mutable, then it *would* be illegal to
> modify it, even if you *can*.
>
> EG:
>
> immutable myFirstRC = RefCounted!int(1);
> immutable myReference = myFirstRC;

This currently wouldn't compile, because all the RefCounted construction/destruction/postblit operations are not pure. But I agree that immutable is a different thing and would potentially break the cast - I was just talking about const references, because this is what frequently happens, even when *not* working with immutable data.

>
> In this particular case, you'd be modifying a ref count that can only be
> accessed via an immutable pointer. As such, the compiler is free to
> assume the value has never been changed, and avoid reading it all
> together, destroying your payload at the end of "myFirstRC"'s life-cycle.
>
> I honestly don't think (given D's transitive constness mechanics), that
> a const RefCount *could* make any sense.
>
> --------
>
> If you have const data referencing mutable data, then yes, you can cast
> away all the const you want, but at that point, it kind of makes the
> whole "const" thing moot.

There is of course always the alternative of using a global array of reference counts to implement this in a clean way, but ideally, there would be a way to semantically separate reference management data from payload data, so that immutable(RefCount!T) can be defined without working around the type system. But let's not clutter up this particular thread with RefCount related issues, it's a separate issue (AFAICS), and I really just brought it up as one example.
May 08, 2014
Am 08.05.2014 18:33, schrieb David Nadlinger:
> On Thursday, 8 May 2014 at 16:30:13 UTC, Sönke Ludwig wrote:
>> For what practical reason would that be the case? I know that the spec
>> states "undefined behavior", but AFAICS, there is neither an existing,
>> nor a theoretical reason, why this should fail:
>
> Compiler optimizations based on immutability.
>
> David

My point was just about const in particular, because RefCount doesn't mix well with immutable anyway (see previous post).
May 08, 2014
Andrei Alexandrescu:

> That's an interesting idea. More generally, there's the notion that making user-defined types as powerful as built-in types is a Good Thing(tm).

Regarding the management of const for library-defined types, sometimes I'd like the type T1 to be seen as equal to the type T2, this could save me some hassles during the usage of tuples:

alias T1 = const Tuple!(int, int);
alias T2 = Tuple!(const int, const int);

I think T1 and T2 should be equivalent for built-in tuples.

Bye,
bearophile
May 08, 2014
On 05/08/2014 06:30 PM, Sönke Ludwig wrote:
> Am 08.05.2014 18:10, schrieb Timon Gehr:
>> On 05/08/2014 06:02 PM, monarch_dodra wrote:
>>>
>>> If you have const data referencing mutable data, then yes, you can cast
>>> away all the const you want, but at that point, it kind of makes the
>>> whole "const" thing moot.
>>
>> This is not guaranteed to work. I guess the only related thing that is
>> safe to do is casting away const, but then not modifying the memory.
>
> For what practical reason would that be the case? I know that the spec
> states "undefined behavior",

Case closed.