January 18, 2018
On Thursday, 18 January 2018 at 07:46:03 UTC, Andrei Alexandrescu wrote:
> There's been some discussion about what to do with issues that propose enhancements like this. We want to make them available and searchable just in case someone working on a related proposal is looking for precedent and inspiration.
>
> I was thinking of closing with REMIND or LATER. Seb is experimenting with moving the entire bug database to github issues, which may offer us more options for classification.

It would make sense to separate bugs from enhancements in this regard. It's useful to record and maintain useful enhancements ideas even if they don't fit the current priorities. There are multiple ways to implement this, but it'd be most useful if the distinction between "bugs" and "enhancements" is obvious and easy to discover.

--Jon
January 18, 2018
On 1/18/18 7:17 AM, rjframe wrote:
> On Thu, 18 Jan 2018 02:46:03 -0500, Andrei Alexandrescu wrote:
> 
>> There's been some discussion about what to do with issues that propose
>> enhancements like this. We want to make them available and searchable
>> just in case someone working on a related proposal is looking for
>> precedent and inspiration.
>>
>> I was thinking of closing with REMIND or LATER.
> 
> If you're specifically talking about enhancements that would require a
> DIP, calling it "DIP needed" (or similar) is clear that work is needed to
> get it in, rather than just some "we'll look at this at some point" kind
> of label.

I put a "DIP needed" mention in text and closed the issue with REMIND. I suggest similar treatment for similar issues. -- Andrei

January 19, 2018
On Thursday, 18 January 2018 at 09:15:04 UTC, Simen Kjærås wrote:
> At any rate, this is a topic for a DIP.

So, a few more thoughts on this:

Arrays and pointers automatically decay to their Unqual'ed variants when passed to a templated function. AAs do not, which makes sense given their reference-type nature. Structs and classes don't, and in general shouldn't. Simple types, like ints and floats, don't.

As a matter of fact, Unqual is too blunt an instrument for what we want to do - Unqual!(const(MyStruct)) == MyStruct, regardless of what's inside MyStruct. For a struct with aliasing (one that contains pointers or arrays), that behavior is plain wrong for our use case.

What is actually needed here is a template that gives the equivalent head-mutable type, such that it can be put in a variable. For const(int), that's int. For const(int[]), int[]. For a const(MyStruct), it's const(MyStruct) or MyStruct, depending on whether MyStruct has aliasing. This test already exists in the compiler (try to assign a const struct {int[] arr; int i;} to a mutable variable, then try the same without the array).

This takes care of the non-templated problems, but the big issue is still ahead of us. For templates, as stated earlier, the connection between T and its head-mutable variant can be arbitrarily complex. However, a single function template is all that's needed to convey all the necessary information. Let's call it opDecay, and give this implementation of the basic logic:

template Decay(T)
{
    import std.traits : Unqual, hasAliasing, isAssociativeArray;
    static if (is(typeof(T.init.opDecay())))
    {
        alias Decay = typeof(T.init.opDecay());
    }
    else static if (is(T == class) || isAssociativeArray!T ||
                   (is(T == struct) && hasAliasing!(Unqual!T)))
    {
        alias Decay = T;
    }
    else
    {
        alias Decay = Unqual!T;
    }
}

unittest
{
    // Regular types:
    assert(is(Decay!(const int) == int));
    assert(is(Decay!(const int*) == const(int)*));
    assert(is(Decay!(const int[]) == const(int)[]));
    assert(is(Decay!(const int[10]) == int[10]));
    assert(is(Decay!(const int[int]) == const(int[int])));

    // Struct without aliasing:
    static struct S1 {
        int n;
        immutable(int)[] arr;
    }
    assert(is(Decay!(const S1) == S1));

    // Struct with aliasing:
    static struct S2 {
        int[] arr;
    }
    assert(is(Decay!(const S2) == const S2));

    // Struct with aliasing, with opDecay hook:
    static struct S3 {
        int[] arr;
        S3 opDecay(this T)() {
            return S3(arr.dup);
        }
    }
    assert(is(Decay!(const S3) == S3));

    // Templated struct with aliasing, with opDecay hook
    struct S4(T) {
        T[] arr;
        auto opDecay(this This)() const {
            import std.traits : CopyTypeQualifiers;
            return S4!(CopyTypeQualifiers!(This, T))(arr.dup);
        }
    }
    assert(is(Decay!(const S4!int) == S4!(const int)));
}

We now have a way of obtaining head-mutable variables of any type that supports it. Alias this takes care of implicit conversion to the decayed type ...except when it doesn't. As stated above, arrays decay when passed to a templated function:

import std.range;

void foo(T)(T arr) {
    assert(is(T == const(int)[]));
    assert(isInputRange!T);
}
unittest {
    const(int[]) a;
    foo(a);
    assert(!isInputRange!(typeof(a)));
}

This behavior is special - no types other than T* and T[] decay in this way, and there's no way to tell the compiler you want your type to do the same. Is it important that our types do the same? I'm not entirely sure. The fact that this decay is inconsistent[1] today makes me even less sure. If we want the same kind of behavior for user-defined types, the compiler will need to insert calls to opDecay when a type with that method is passed to a function[2].

opDecay is likely to be a small function that can be inlined and in many cases elided altogether, and will only be used for a small subset of types, so I believe the overhead of calling it whenever a type with the method is passed to another function, would be negligible.

Destroy!

--
  Simen

[1]: https://issues.dlang.org/show_bug.cgi?id=18268
[2]: If typeof(x.opDecay) != typeof(x), and the type of the parameter is not explicitly typeof(x).
1 2
Next ›   Last »