unittest {
import std.complex : abs;
import std.math : abs;
// template std.complex.abs cannot deduce function
// from argument types !()(int)
auto a = abs(1);
}
Changing the order of imports above makes the code compile.
Using regular imports correctly merges overload sets:
unittest {
import std.complex;
import std.math;
auto a = abs(1);
}
This is likely related to the restrictions on overload sets in function scopes:
unittest {
void fun() {}
void fun(int i) {} // declaration fun is already defined
}
Comment #1 by simen.kjaras — 2019-09-19T07:26:08Z
Note that even though changing the order of imports makes the above code compile, that's because std.math's abs() implementation ends up being the one available, and calling abs() on e.g. a Complex!float would fail in that case (but would compile in the above example).
This same issue occurs when importing a function that also exists in a parent scope:
import std.stdio;
float abs(float f) {
return f < 0 ? -f : f;
}
unittest {
import std.complex : complex, abs;
auto a = complex(1.0,1.0);
auto b = 1.0f;
writeln(abs(a));
writeln(abs(b));
}
In a non-function scope, the solution is to use an alias:
float abs(float f) {
return f < 0 ? -f : f;
}
struct S {
import std.complex : complex, abs;
alias abs = .abs;
unittest {
abs(1);
abs(complex(1,1));
}
}
However, this doesn't work inside functions:
float abs(float f) {
return f < 0 ? -f : f;
}
unittest {
import std.complex : complex, abs;
alias abs = .abs; // declaration abs is already defined
}
A workaround exists in using a non-function scope to merge the overload sets:
float abs(float f) {
return f < 0 ? -f : f;
}
unittest {
import std.complex : complex, cabs = abs;
alias abs = MergeOverloads!(cabs, .abs);
abs(1);
abs(complex(1,1));
}
template MergeOverloads(T...) {
static foreach (E; T)
alias MergeOverloads = E;
}
Comment #2 by robert.schadek — 2024-12-13T19:05:37Z