Thread overview
Value based overload resolution?
Nov 09, 2020
kdevel
Nov 09, 2020
Adam D. Ruppe
Nov 09, 2020
Paul Backus
November 09, 2020
Today I came across this:

~~~id.d
import std.stdio : writeln;

T foo (T) (T s)
{
   __PRETTY_FUNCTION__.writeln;
   return s;
}

short foo (short s)
{
   __PRETTY_FUNCTION__.writeln;
   return s;
}

T id (T) (T t)
{
   return t;
}

int main ()
{
   foo (1);
   foo (1L);
   foo (id (1));
   foo (id (1L));
   foo (0x1_0000);
   foo (0x1_0000L);
   return 0;
}
~~~

Output:

$ ./id
short id.foo(short s)
short id.foo(short s)
int id.foo!int.foo(int s)
long id.foo!long.foo(long s)
int id.foo!int.foo(int s)
long id.foo!long.foo(long s)

It appears to me that the overload resolution may depend on the /value/ of the function argument. According to [1] the type of 1 is int and that of 1L is long. Thus I would have expected foo!int and foo!long being called in those cases.

[1] https://dlang.org/spec/lex.html#integerliteral
November 09, 2020
On Monday, 9 November 2020 at 22:04:55 UTC, kdevel wrote:
> It appears to me that the overload resolution may depend on the /value/ of the function argument. According to [1] the type of 1 is int and that of 1L is long.

That's not exactly true because of value range propagation. It is weird in this case.

But the intention is for stuff like

short a = 1;

to work without an explicit cast from 1 being an int. So the compiler looks at what it can see in the expression - exact values of literals, ranges of possible values from other variables - and determines the smallest type it can fit inside. Then it automatically assigns it that type instead.


What gets pretty weird is if you add a bool overload and pass 1.... the compiler considers bool to be a very restrained integral type... and it knows the values of `enum` too which can give some surprising results.

But yeah the values or the possible ranges of values can actually affect the types of arithmetic expressions and this is considered in overloading for better or for worse.
November 09, 2020
On Monday, 9 November 2020 at 22:04:55 UTC, kdevel wrote:
> It appears to me that the overload resolution may depend on the /value/ of the function argument. According to [1] the type of 1 is int and that of 1L is long. Thus I would have expected foo!int and foo!long being called in those cases.
>
> [1] https://dlang.org/spec/lex.html#integerliteral

As you've discovered, the types of integer literals (and literals in general) is somewhat fluid: the *default* type of `1` is `int`, but the compiler may infer a different type based on the value, or on the context in which the literal is used.

For example:

static assert(is(typeof([1, 2, 3]) == int[]));
int[] a = [1, 2, 3];
ubyte[] b = [1, 2, 3];

Perhaps even more confusingly, this also applies to manifest (enum) constants:

enum literal = [1, 2, 3];
static assert(is(typeof(literal) == int[]));
int[] a = literal;
ubyte[] b = literal;