Why is following code different behavior between int and string?
-----------------
import std;
struct S
{
int opApply(int delegate(ref const(int), ref int) dg)
{ writeln("mutable int"); return 0; }
int opApply(int delegate(ref const(int), ref const(int)) dg) const
{ writeln("const int"); return 0; }
int opApply(int delegate(ref const(int), ref string) dg)
{ writeln("mutable str"); return 0; }
int opApply(int delegate(ref const(int), ref const(string)) dg) const
{ writeln("const str"); return 0; }
}
void main()
{
S s;
foreach (ref const(int) x, ref const(int) y; s) {}
foreach (ref const(int) x, ref const(string) y; s) {}
}
-----------------
rdmd playground.d
const int
mutable str
-----------------
The function on the const one that matches better should be called.
This problem was discovered by investigating the cause of Botan(*) build failure in 2.099.1-beta.1.
However, the above code shows the same behavior since before 2.098.1.
* https://github.com/etcimon/botan
Comment #1 by moonlightsentinel — 2022-02-20T12:02:11Z
> The function on the const one that matches better should be called.
Neither of the string opApply's is an exact match due to the const missmatch on `this` or the delegate parameter => "match with implicit conversions". Partial ordering then selects the mutable overload because it is "more specialized" (cannot be called by the const overload).
Not sure why the int overloads expose a different behaviour.
See https://dlang.org/spec/function.html#function-overloading
Comment #2 by moonlightsentinel — 2022-02-20T12:03:07Z
Slightly modified test case:
import core.stdc.stdio;
struct S
{
int opApply(int delegate(ref const(int), ref int) dg)
{ puts("mutable int"); return 0; }
int opApply(int delegate(ref const(int), ref const(int)) dg) const
{ puts("const int"); return 0; }
int opApply(int delegate(ref const(int), ref string) dg)
{ puts("mutable str"); return 0; }
int opApply(int delegate(ref const(int), ref const(string)) dg) const
{ puts("const str"); return 0; }
}
void main()
{
S s;
foreach (ref const(int) x, ref const(int) y; s) {}
foreach (ref const(int) x, ref const(string) y; s) {}
const S s2;
foreach (ref const(int) x, ref const(int) y; s2) {}
foreach (ref const(int) x, ref const(string) y; s2) {}
}
Up to 2.074.1: Success with output:
-----
const int
const str
const int
const str
-----
Since 2.075.1: Success with output:
-----
const int
mutable str
const int
const str
-----
Comment #3 by moonlightsentinel — 2022-02-20T12:17:41Z
Same behaviour without the first parameter that is identical for every overload:
struct S
{
int opApply(int delegate(ref int) dg) { puts("mutable int"); return 0; }
int opApply(int delegate(ref const(int)) dg) const { puts("const int"); return 0; }
int opApply(int delegate(ref string) dg) { puts("mutable str"); return 0; }
int opApply(int delegate(ref const(string)) dg) const { puts("const str"); return 0; }
}
void main()
{
S s;
foreach (ref const(int) y; s) {}
foreach (ref const(string) y; s) {}
const S s2;
foreach (ref const(int) y; s2) {}
foreach (ref const(string) y; s2) {}
}
Comment #4 by moonlightsentinel — 2022-02-20T12:21:24Z
(In reply to moonlightsentinel from comment #1)
> [...] "match with implicit conversions".
Correction: This applies only to the mutable overload because the delegate type changes due to the different constness. The const overload should be "match with qualifier conversion" (`S` => `const S`).
Comment #5 by robert.schadek — 2024-12-13T19:21:01Z