May 08, 2014
On 05/08/2014 08:55 AM, Jonathan M Davis via Digitalmars-d wrote:
> As far as I can see, opByValue does the same thing as opSlice, except
> that it's used specifically when passing to functions, whereas this code
>
> immutable int [] a = [1, 2, 3];
> immutable(int)[] b = a[];
>
> or even
>
> immutable int [] a = [1, 2, 3];
> immutable(int)[] b = a;
>
> compiles just fine. So, I don't see how adding opByValue helps us any.
> Simply calling opSlice implicitly for user-defined types in the same
> places that it's called implicitly on arrays would solve that problem.
> We may even do some of that already, though I'm not sure.

Automatic slicing on function call is not what actually happens. You can see this more clearly if you pass an immutable(int*) instead of an immutable(int[]): there is no way to slice the former and the mechanism that determines the parameter type to be immutable(int)* is the same. (And indeed doing the opSlice would have undesirable side-effects, for example, your pet peeve, implicit slicing of stack-allocated static arrays would happen on any IFTI call with a static array argument. :o) Also, other currently idiomatic containers couldn't be passed to functions.)
May 08, 2014
On 05/08/2014 09:01 AM, Jonathan M Davis via Digitalmars-d wrote:
> It really has nothing to do with passing an argument to a function
> beyond the fact that that triggers an implicit call to the slice operator.

module check_claim;
import std.stdio;

auto foo(immutable(int)[] a){writeln("Indeed, this is what happens.");}
auto foo(immutable(int[]) a){writeln("No, this is not what happens.");}

void main(){
    immutable(int[]) x;
    foo(x);
}

$ dmd -run check_claim
No, this is not what happens.
May 08, 2014
On Thu, 08 May 2014 12:38:44 +0200
Timon Gehr via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> On 05/08/2014 08:55 AM, Jonathan M Davis via Digitalmars-d wrote:
> > As far as I can see, opByValue does the same thing as opSlice, except that it's used specifically when passing to functions, whereas this code
> >
> > immutable int [] a = [1, 2, 3];
> > immutable(int)[] b = a[];
> >
> > or even
> >
> > immutable int [] a = [1, 2, 3];
> > immutable(int)[] b = a;
> >
> > compiles just fine. So, I don't see how adding opByValue helps us any. Simply calling opSlice implicitly for user-defined types in the same places that it's called implicitly on arrays would solve that problem. We may even do some of that already, though I'm not sure.
>
> Automatic slicing on function call is not what actually happens. You can see this more clearly if you pass an immutable(int*) instead of an immutable(int[]): there is no way to slice the former and the mechanism that determines the parameter type to be immutable(int)* is the same. (And indeed doing the opSlice would have undesirable side-effects, for example, your pet peeve, implicit slicing of stack-allocated static arrays would happen on any IFTI call with a static array argument. :o) Also, other currently idiomatic containers couldn't be passed to functions.)

Ah, you're right. The fact that slicing an array results in tail-const array is part of the equation, but implicit slicing isn't necessarily (though in the case of IFTI, you still get an implicit slice - it's just that that's a side effect of what type the parameter is inferred as).

Still, the core problem is that MyRange!(const T) is not the same as const(MyRange!T), and that needs to be solved for opSlice to work properly. I'd still be inclined to try and just solve the problem with opSlice rather than introducing opByValue (maybe by having a UDA on opSlice which indicates that it should be implicitly sliced in the same places that a dynamic array would?), but as you point out, the problem is unfortunately a bit more complicated than that.

- Jonathan M Davis
May 08, 2014
On Thursday, 8 May 2014 at 03:58:16 UTC, Andrei Alexandrescu wrote:
> This change would allow library designers to provide good solutions to making immutable and const ranges work properly - the way T[] works.
>
> There are of course a bunch of details to think about and figure out, and this is a large change. Please chime in with thoughts. Thanks!
>
> Andrei

The only thing I'd be afraid about is calling a run-time *function* when you pass something by value. It seems like it creates a *huge* hole for abuse.

I'd be OK if "opByValue" was allowed only as an alias type. EG, something like:

struct S(T)
{
    alias opByValue(const) = S(const(T));
}

Which would (statically) mean that "const(S(T))" may (and should) be value converted to S(const(T));

This would still need a bit of work, but I think having a hidden function call for pass by value is a Bad Thing (tm)
May 08, 2014
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.
May 08, 2014
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. Also, if you take the other example, Isolated!T, there is no reference count involved and const(Isolated!T) <-> Isolated!(const(T)) would be exactly what is needed.
May 08, 2014
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.
May 08, 2014
On 05/08/2014 01:05 PM, monarch_dodra wrote:
> ...
> In fact, I'm wondering if this might not be a more interesting direction
> to explore.

http://forum.dlang.org/thread/ljrm0d$28vf$1@digitalmars.com?page=3#post-ljrt6t:242fpc:241:40digitalmars.com

http://forum.dlang.org/thread/ljrm0d$28vf$1@digitalmars.com?page=7#post-ljt0mc:24cto:241:40digitalmars.com
May 08, 2014
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

May 08, 2014
On Thu, May 08, 2014 at 11:05:19AM +0000, monarch_dodra via Digitalmars-d wrote:
> 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.

+1.

I don't like opByValue because it seems to be too special-cased. The *real* issue we need to grapple with is head- vs. tail-const, and how to interconvert between them in user-defined types. As others have already pointed out, opByValue is really not that much different from opSlice (arguably not different at all). It seems such a waste to spend so much effort on it when we have much bigger fish to fry -- tail-const.


T

-- 
You are only young once, but you can stay immature indefinitely. -- azephrahel