April 01, 2021
On 4/1/21 5:59 PM, H. S. Teoh wrote:

> Now, when you append a string to a const(char)[], the compiler has to
> promote `string` to `const(char)[]` first, so that the operands of ~
> have the same type. And obviously, the result of concatenating two
> const(char)[] must be const(char)[], since you don't know if one of them
> may have mutable aliases somewhere else.  So the result must likewise be
> const(char)[].

But it's not.

auto z = x ~ y;
pragma(msg, typeof(z)); // char[]

This is what's confusing me. The compiler somehow knows it can do this implicit cast, but doesn't know that the result is unique. It should be obvious.

> One may argue that appending in general will reallocate, and once
> reallocated it will be unique, and there safe to implicitly convert to
> immutable.  However, in general we cannot guarantee this, e.g., one of
> the strings could be empty and not reallocate at runtime, so it may
> continue to be aliased by some mutable reference somewhere else. So the
> result must be typed as const(char)[], along with the restriction that
> it cannot implicitly convert to immutable.

a ~ b will always allocate new memory, it's in the spec.

-Steve
April 01, 2021
On 4/1/21 5:51 PM, Per Nordlöw wrote:
> On Thursday, 1 April 2021 at 21:41:06 UTC, Steven Schveighoffer wrote:
>> I'm surprised it doesn't return const(char)[].
> 
> I think it returning `char[]` is sound.

Not saying it's unsound, it just surprises me.

> What's unsound is that the compiler lacks knowledge of it being a unique slice (no aliasing).

It shouldn't lack this knowledge. That's not soundness, it's just annoying.

I'm not saying I disagree with you, it's a limitation and I think there's no good explanation for the problem.

To illustrate the point more cleanly:

```d
auto concat(T, U)(T[] x, U[] y) pure
{
    return x ~ y;
}

void main()
{
    string x;
    const(char)[] y;
    string z = concat(x, y); // compiles
}
```

-Steve
April 01, 2021
On Thu, Apr 01, 2021 at 03:07:09PM -0700, Ali Çehreli via Digitalmars-d wrote:
> On 4/1/21 2:59 PM, H. S. Teoh wrote:
[...]
> > One may argue that appending in general will reallocate, and once reallocated it will be unique, and there safe to implicitly convert to immutable.  However, in general we cannot guarantee this
> 
> Yes, that's tricky for append because one of many slices does own the potential bytes after the array and will append elements in there. However, concatenation always makes a new array, right? I think the result can be char[] in that case.
[...]

If one of the arguments is an empty array, does concatenation allocate a new array anyway? Or does it simply return the other argument? (I don't know.)  If not, then we cannot make it implicitly convertible.


T

-- 
Genius may have its limitations, but stupidity is not thus handicapped. -- Elbert Hubbard
April 01, 2021
On 4/1/21 6:34 PM, H. S. Teoh wrote:
> On Thu, Apr 01, 2021 at 03:07:09PM -0700, Ali Çehreli via Digitalmars-d wrote:
>> On 4/1/21 2:59 PM, H. S. Teoh wrote:
> [...]
>>> One may argue that appending in general will reallocate, and once
>>> reallocated it will be unique, and there safe to implicitly convert
>>> to immutable.  However, in general we cannot guarantee this
>>
>> Yes, that's tricky for append because one of many slices does own the
>> potential bytes after the array and will append elements in there.
>> However, concatenation always makes a new array, right? I think the
>> result can be char[] in that case.
> [...]
> 
> If one of the arguments is an empty array, does concatenation allocate a
> new array anyway? Or does it simply return the other argument? (I don't
> know.)  If not, then we cannot make it implicitly convertible.

Yes, always an allocation. See point 5 here: https://dlang.org/spec/arrays.html#array-concatenation

-Steve
April 01, 2021

On Thursday, 1 April 2021 at 22:35:57 UTC, Steven Schveighoffer wrote:

>

Yes, always an allocation. See point 5 here: https://dlang.org/spec/arrays.html#array-concatenation

Good, then the implicit conversion should be allowed. Anybody up for the job? If not, I'm gonna look into it.

April 01, 2021

On Thursday, 1 April 2021 at 21:59:21 UTC, H. S. Teoh wrote:

>

Just use .idup on the result.

In the meanwhile .assumeUnique from std.exception should be preferred over .idup unless duplicate memory allocations is of intrinsic value. ;)

April 01, 2021
On Thu, Apr 01, 2021 at 06:34:04PM -0400, Steven Schveighoffer via Digitalmars-d wrote: [...]
> ```d
> auto concat(T, U)(T[] x, U[] y) pure
> {
>     return x ~ y;
> }
> 
> void main()
> {
>     string x;
>     const(char)[] y;
>     string z = concat(x, y); // compiles
> }
> ```
[...]

Put this way, the solution becomes obvious: `~` should be considered a pure operation.  Then the compiler (in theory) ought to be able to infer uniqueness from `x ~ y`, and consequently allow implicit conversion to immutable.


T

-- 
Never criticize a man until you've walked a mile in his shoes. Then when you do criticize him, you'll be a mile away and he won't have his shoes.
April 01, 2021

On Thursday, 1 April 2021 at 22:34:04 UTC, Steven Schveighoffer wrote:

>

To illustrate the point more cleanly:

auto concat(T, U)(T[] x, U[] y) pure
{
    return x ~ y;
}

void main()
{
    string x;
    const(char)[] y;
    string z = concat(x, y); // compiles
}

Found by accident that the code does not compile with -dip1000 on 2.095 (run.dlang.org).

Actually, I tried inclining the function template and to my surprise, whether it passes or not depends on -dip1000:

    string z1 = (() => x ~ y)(); // fails with and without -dip1000
    string z2 = ((x, y) => x ~ y)(x, y); // passes without, fails with -dip1000

It makes no difference adding pure to any of those since it's inferred.

April 01, 2021
On 4/1/21 3:55 PM, H. S. Teoh wrote:
> On Thu, Apr 01, 2021 at 06:34:04PM -0400, Steven Schveighoffer via Digitalmars-d wrote:
> [...]
>> ```d
>> auto concat(T, U)(T[] x, U[] y) pure
>> {
>>      return x ~ y;
>> }
>>
>> void main()
>> {
>>      string x;
>>      const(char)[] y;
>>      string z = concat(x, y); // compiles
>> }
>> ```
> [...]
> 
> Put this way, the solution becomes obvious: `~` should be considered a
> pure operation.  Then the compiler (in theory) ought to be able to
> infer uniqueness from `x ~ y`, and consequently allow implicit
> conversion to immutable.
> 
> 
> T
> 

I admit I've been neglecting indirections. If we are dealing with const(S)[], S being a user defined type with indirections, then concatenation cannot be S[] because the original S object would not allow mutation through them.

This is still within compiler's attribute inference, right? On the other hand, this probably would complicate template code: I can imagine a template code is tested with simple types and works but fails as soon as used with a const(S)[] type at a customer site.

Ali
April 01, 2021
On 4/1/21 3:44 PM, Per Nordlöw wrote:
> On Thursday, 1 April 2021 at 22:35:57 UTC, Steven Schveighoffer wrote:
>> Yes, always an allocation. See point 5 here: https://dlang.org/spec/arrays.html#array-concatenation
> 
> Good, then the implicit conversion should be allowed. Anybody up for the job? If not, I'm gonna look into it.

As I mentioned elsewhere in this thread, the element type must not have indirections though. If S is a struct with indirections, concatenating const(S)[] should still produce const(S)[].

Ali