Thread overview
const(Rvalue) resolved to different overloads
Dec 29, 2016
Ali Çehreli
Dec 30, 2016
Jonathan M Davis
Dec 30, 2016
Ali Çehreli
Dec 31, 2016
Timon Gehr
Jan 01, 2017
Ali Çehreli
Jan 01, 2017
safety0ff
Jan 01, 2017
Ali Çehreli
December 29, 2016
I'm working on understanding how different qualifiers of the same type, the kinds of indirections that its members may have, and the expressions being lvalue versus rvalue affect function overload resolution.

For example, the following program has

- struct S with a member having const indirection

- Two overloads of the same function taking S and immutable(S)

- Two calls made with an Rvalue and a const Rvalue

import std.stdio;

struct S {
    const(int)[] a;
}

void foo(S s) {
    writefln("foo(S) called");
}

void foo(immutable(S) s) {
    writefln("foo(immutable(S)) called");
}

void main() {
    writefln("calling with Rvalue");
    foo(S());

    writefln("calling with const Rvalue");
    foo(const(S)());
}

The peculiar thing is that the const Rvalue case is resolved to the immutable(S) overload:

calling with Rvalue
foo(S) called
calling with const Rvalue
foo(immutable(S)) called    <-- ?

Can you explain that behavior?

Well... I already see that for that to happen, the argument must have the S.init value (or be constructed explicitly as const(S)(null)). When initialized with something else, it is resolved to the foo(S) overload:

    const(int)[] a = [ 42 ];
    foo(const(S)(a));

calling with const Rvalue
foo(S) called               <-- Now different; surprising

Bug or not? If not, then I don't think there can ever be more than a total of about 0.75 people who fully know D overload resolution. :o)

Ali
December 29, 2016
On Thursday, December 29, 2016 14:54:35 Ali Çehreli via Digitalmars-d wrote:
> I'm working on understanding how different qualifiers of the same type, the kinds of indirections that its members may have, and the expressions being lvalue versus rvalue affect function overload resolution.
>
> For example, the following program has
>
> - struct S with a member having const indirection
>
> - Two overloads of the same function taking S and immutable(S)
>
> - Two calls made with an Rvalue and a const Rvalue
>
> import std.stdio;
>
> struct S {
>      const(int)[] a;
> }
>
> void foo(S s) {
>      writefln("foo(S) called");
> }
>
> void foo(immutable(S) s) {
>      writefln("foo(immutable(S)) called");
> }
>
> void main() {
>      writefln("calling with Rvalue");
>      foo(S());
>
>      writefln("calling with const Rvalue");
>      foo(const(S)());
> }
>
> The peculiar thing is that the const Rvalue case is resolved to the
> immutable(S) overload:
>
> calling with Rvalue
> foo(S) called
> calling with const Rvalue
> foo(immutable(S)) called    <-- ?
>
> Can you explain that behavior?
>
> Well... I already see that for that to happen, the argument must have
> the S.init value (or be constructed explicitly as const(S)(null)). When
> initialized with something else, it is resolved to the foo(S) overload:
>
>      const(int)[] a = [ 42 ];
>      foo(const(S)(a));
>
> calling with const Rvalue
> foo(S) called               <-- Now different; surprising
>
> Bug or not? If not, then I don't think there can ever be more than a total of about 0.75 people who fully know D overload resolution. :o)

I very much doubt that it's a bug, since it seems like the sort of thing that would require extra logic to make happen. That being said, I think that an argument could definitely be made that it was a bad decision from the standpoint of consistency (and it's quite possible that it's an accident that it works the way it does thanks to some weird combination of features). It wouldn't entirely surprise me though if the current behavior is a result of someone wanting a particular piece of code to work that wouldn't work otherwise. In practice, I wouldn't expect it to matter - and usually if you've overloaded on mutable and immutable, you're going to want to overload on const as well anyway - but the current behavior does make it entertaining to figure out what's going to happen in corner cases, which isn't exactly a good thing and goes counter to a lot of the reasoning behind why D's overloading rules generally work the way they work.

- Jonathan M Davis


December 30, 2016
On 12/29/2016 11:59 PM, Jonathan M Davis via Digitalmars-d wrote:

> In practice, I wouldn't expect it to matter - and usually if
> you've overloaded on mutable and immutable, you're going to
> want to overload on const as well anyway

Coming up with such guidelines is exactly the reason why I was experimenting with different combinations of overloads.

> but the current behavior does make it entertaining to figure
> out what's going to happen in corner cases

There are too many corner cases when one considers rvalue versus lvalue, const or immutable member indirections, const or immutable parameters, const or immutable expressions. :) Then there is 'auto ref' which competes with some of these cases.

Ali

December 31, 2016
On 30.12.2016 08:59, Jonathan M Davis via Digitalmars-d wrote:
>> Bug or not? If not, then I don't think there can ever be more than a
>> total of about 0.75 people who fully know D overload resolution. :o)

It's a bug.

https://dlang.org/spec/function.html#function-overloading

The following case is essentially identical:

import std.stdio;

int foo(int x){ return 1; }
int foo(immutable(int) x){ return 2; }

void main(){
    const int x=2;
    writeln(foo(x)); // error
}

The reason why rvalue vs. lvalue can make a difference is implicit conversion to immutable for provably unique references:

struct S {
    const(int)[] a;
}

void main(){
    immutable s = const(S)(null); // ok
    const t = const(S)(null);
    immutable u = t; // error
}

(I.e., the lvalue does not match the immutable overload at all.)


> I very much doubt that it's a bug, since it seems like the sort of thing
> that would require extra logic to make happen.

Bugs are wrong logic, often found within unnecessarily convoluted extra logic.


Similar case:

class C{}
struct S{
    const(C) c;
}
int foo(S c){ return 1; }
int foo(immutable(S) c){ return 2; }

void main(){
    writeln(foo(const(S)(new C))); // 2
}

Note that the behaviour changes if the type of 'c' is changed to C or immutable(C) instead of const(C).

The rule that DMD applies seems to be: if there is at least one const indirection and all other indirections are either const or immutable, pick the immutable overload. Otherwise, error.

This is not the kind of detail that should matter for overload resolution (especially given that fields can be private).
December 31, 2016
On 12/31/2016 06:22 AM, Timon Gehr wrote:

> This is not the kind of detail that should matter for overload
> resolution (especially given that fields can be private).

Filed:

  https://issues.dlang.org/show_bug.cgi?id=17050

Ali

January 01, 2017
On Thursday, 29 December 2016 at 22:54:35 UTC, Ali Çehreli wrote:
>
> Can you explain that behavior?

What about: http://dlang.org/spec/const3.html#implicit_conversions
"An expression may be converted from mutable or shared to immutable if the expression is unique and all expressions it transitively refers to are either unique or immutable."
December 31, 2016
On 12/31/2016 09:18 PM, safety0ff wrote:
> On Thursday, 29 December 2016 at 22:54:35 UTC, Ali Çehreli wrote:
>>
>> Can you explain that behavior?
>
> What about: http://dlang.org/spec/const3.html#implicit_conversions
> "An expression may be converted from mutable or shared to immutable if
> the expression is unique and all expressions it transitively refers to
> are either unique or immutable."

Good find but its effect in overload resolution is an oversight.

The uniqueness of an expression should not interfere with overload resolution because overload resolution does not involve expressions but their types. Otherwise, a simple change in an object initialization takes us to a different function. A different kind of hijacking indeed...

Ali