Thread overview
Type of string literal concatenated with non-immutable char array
Jan 31, 2021
Per Nordlöw
Feb 01, 2021
Jacob Carlborg
Feb 01, 2021
Per Nordlöw
Feb 01, 2021
ag0aep6g
Feb 01, 2021
Per Nordlöw
Feb 01, 2021
Per Nordlöw
January 31, 2021
Given

    char x[];

why is

    typeof("a" ~ x)

`char[]` when

    typeof("a" ~ x.idup)

is

    `string`?

My case is

class NameLookupException : Exception
{
    this(string name) {
        super("Name " ~ name ~ " could not be found");
    }
    this(scope const(char)[] name) {
        super("Name " ~ name.idup ~ " could not be found");
    }
}

where I instead would like to only need

class NameLookupException : Exception
{
    this(scope const(char)[] name) {
        super("Name " ~ name ~ " could not be found");
    }
}

Why isn't

    "Name " ~ name ~ " could not be found"

implicitly convertible to `string`?

Would

class NameLookupException : Exception
{
    this(scope const(char)[] name) @trusted {
        super("Name " ~ cast(string)name ~ " could not be found");
    }
}

be ok?
February 01, 2021
On Sunday, 31 January 2021 at 21:48:09 UTC, Per Nordlöw wrote:
> Given
>
>     char x[];
>
> why is
>
>     typeof("a" ~ x)
>
> `char[]` when
>
>     typeof("a" ~ x.idup)
>
> is
>
>     `string`?
>
> My case is
>
> class NameLookupException : Exception
> {
>     this(string name) {
>         super("Name " ~ name ~ " could not be found");
>     }
>     this(scope const(char)[] name) {
>         super("Name " ~ name.idup ~ " could not be found");
>     }
> }
>
> where I instead would like to only need
>
> class NameLookupException : Exception
> {
>     this(scope const(char)[] name) {
>         super("Name " ~ name ~ " could not be found");
>     }
> }
>
> Why isn't
>
>     "Name " ~ name ~ " could not be found"
>
> implicitly convertible to `string`?

Because if you have arrays of reference types, it's possible to change an element of the mutable array, which will affect the immutable array, those breaking the immutability. Example:

class Foo
{
    int a;
}

void main()
{
    Foo[] a = [new Foo];
    immutable(Foo)[] b = [new Foo]; // `string` is an alias for `immutable(char)[]`

    auto c = b ~ a;
    a[0].a = 3;
    assert(c[1].a == 3);
}

Due to language consistency it should behave the same for all types.

In the above example, `c` is typed as `const(Foo)[]`. Although, I wonder why in your example the concatenation is typed as `char[]` instead of `const(char)[]`. Perhaps that's a bug.

--
/Jacob Carlborg
February 01, 2021
On Monday, 1 February 2021 at 10:27:29 UTC, Jacob Carlborg wrote:
>> Why isn't
>>
>>     "Name " ~ name ~ " could not be found"
>>
>> implicitly convertible to `string`?
>
> Because if you have arrays of reference types, it's possible to change an element of the mutable array, which will affect the immutable array, those breaking the immutability. Example:

I still don't understand why that restriction applies to arrays of values types (such as `char`). Having this limitation makes my code example more bloated or less efficient memorywise; I either have to define two separate ctors or force an .idup at the site of the exception construction.
February 01, 2021
On 31.01.21 22:48, Per Nordlöw wrote:
> Why isn't
> 
>      "Name " ~ name ~ " could not be found"
> 
> implicitly convertible to `string`?

If concatenation is guaranteed to allocate a new array, then it should be "strongly pure", and the conversion should work. I'm not sure if it is guaranteed to allocate a new array.

> Would
> 
> class NameLookupException : Exception
> {
>      this(scope const(char)[] name) @trusted {
>          super("Name " ~ cast(string)name ~ " could not be found");
>      }
> }
> 
> be ok?

Only if you know for sure that you're dealing with a compiler bug here.

As another workaround, you can use std.conv.text:

    import std.conv: text;
    super(text("Name ", name, " could not be found"));
February 01, 2021
On 1/31/21 4:48 PM, Per Nordlöw wrote:

> Why isn't
> 
>      "Name " ~ name ~ " could not be found"
> 
> implicitly convertible to `string`?

One of my oldest Enhancement requests: https://issues.dlang.org/show_bug.cgi?id=1654

-Steve
February 01, 2021
On 2/1/21 7:17 AM, ag0aep6g wrote:
> On 31.01.21 22:48, Per Nordlöw wrote:
>> Why isn't
>>
>>      "Name " ~ name ~ " could not be found"
>>
>> implicitly convertible to `string`?
> 
> If concatenation is guaranteed to allocate a new array, then it should be "strongly pure", and the conversion should work. I'm not sure if it is guaranteed to allocate a new array.

It is. https://dlang.org/spec/arrays.html#array-concatenation

"Concatenation always creates a copy of its operands, even if one of the operands is a 0 length array"

-Steve
February 01, 2021
On Monday, 1 February 2021 at 15:47:33 UTC, Steven Schveighoffer wrote:
> "Concatenation always creates a copy of its operands, even if one of the operands is a 0 length array"

Btw, does

    x ~ y ~ z ~ ...

result in a single allocation?
February 01, 2021
On Monday, 1 February 2021 at 16:30:31 UTC, Per Nordlöw wrote:
> Btw, does
>
>     x ~ y ~ z ~ ...
>
> result in a single allocation?

Provided all the operands has a `length` property.
February 01, 2021
On 2/1/21 11:31 AM, Per Nordlöw wrote:
> On Monday, 1 February 2021 at 16:30:31 UTC, Per Nordlöw wrote:
>> Btw, does
>>
>>     x ~ y ~ z ~ ...
>>
>> result in a single allocation?
> 
> Provided all the operands has a `length` property.

As long as they are all arrays, yes, the compiler calls a single runtime call to concatenate all of them.

For custom types, it's going to do them in order, 2 at a time.

-Steve