free func "front" is not picked up as a candidate when doing range.front(...)

aliak something at something.com
Sun Feb 11 13:23:40 UTC 2018


On Thursday, 8 February 2018 at 22:57:04 UTC, Jonathan M Davis 
wrote:
> D tends to be very picky about what it puts in overload sets in 
> order to avoid function hijacking - e.g. it doesn't even 
> include base class functions in an overload set once you've 
> declared one in a derived class unless you explicitly add an 
> alias to the base class function in the derived class.
>
> Also, D doesn't support "best match" with function overloads. 
> The matches have to be exact - e.g. as soon as implicit 
> conversions come into play, there can only be one match. If 
> there's ever a conflict, you get a compilation error (unlike in 
> C++, which tries to figure out what it thinks the best match is 
> and go with that, sometimes with surprising results). And 
> having both the member functions and free functions in the same 
> overload set would basically require the compiler to go with 
> the "best match," which simply isn't how D deals with 
> overloads. Sometimes, that's annoying, but it also prevents a 
> lot of subtle bugs that tend to crop up in C++ code.
> 
> https://dlang.org/spec/function.html#function-overloading 
> https://dlang.org/articles/hijack.html
>
> - Jonathan M Davis

Thanks for the articles, they shed a bit more understanding.

But I have some concerns/issues that maybe you or others can help 
out with a bit more.

First, the link on function-overloading says "The function with 
the best match is selected. " So I'm not sure what you mean about 
subtle bugs in C++ and D doesn't support best match. If you have 
foo(long), food(int) and call foo(3) in the same module, the best 
match is selected in D and in C++. But, anyway, ignoring C++ for 
now, after reading those articles you linked to my main issues 
are D related, not C++, let me try to explain:

The rules of resolving overload sets seem to be to resolve the 
issue where two overload sets have a matching function and one is 
silently better. This is understandable and makes sense.

The case of a free function not being considered if there is a 
member functions seems unrelated (as you say they are not part of 
the overload set, but it seems like like maybe they should be). 
In this case the member function does not match. If overload set 
resolution was applied the free function would be considered and 
be chosen as the only available candidate.

The rules for overriding, where functions with the same name in a 
base class are hidden, also make sense. But not relevant here. 
Infact, the hijacking article makes it seem to me that ufcs 
hijacking has maybe been overlooked? Consider two modules which 
are unrelated:

module a;

string f(R)(R r, int i) {
     return "a.f";
}

string g(R)(R r) {
     return "a.g";
}

---------

module b;

auto algo() {
     static struct R {
         void popFront() {}
         enum empty = false;
         int front() { return 1; }
     }
     return R();
}

----------
// Application programmer;

import std.stdio: writeln;
import a: f, g;
import b: algo;

void main(string[] args) {
     auto r = algo();
     auto a = r.f(3);
     auto b = r.g;
     writeln(a); // will print a.f
     writeln(b); // will print a.g
}

All good so far. Until module b decides to add an internal 
function to it's R type:

auto algo() {
     static struct R {
         // Add a function
         string g() { return "internal g"; }
     }
     return R();
}

Application programmer updates module b...

auto b = r.g; // Does not do what programmer thinks anymore, and 
no warnings.

Because a.g does not participate in overload set resolution 
rules, the application programmer has an unknown bug (is it just 
me or is this a bit... to put it lightly... scary?)

So that's the first problem. Then, secondly, say the people who 
make module b like using proper access control levels, and mark 
the internal string g() as private. Now there's a compiler error 
in the application programmer, with no real clues as to why, 
unless you deep dive in to D lang specs. And then you find out 
you're getting a compiler error because of a private, internal, 
function in a Voldermort type... ouch. Like. Big ouch. But ok, 
this can at least be worked around:

import a: f, c = g;

Now:

auto b = r.c; // ok.

Third problem:

Module b person adds another internal function to their type.

auto algo() {
     static struct R {
         // Add a function
         string f() { return "internal f"; }
     }
     return R();
}

Now application programmer has a compile error again. Again with 
no clue. But this time it makes no sense because the line:

auto a = r.f(3);

Is clearly and unambiguously calling a.f(int). Again here if the 
free function was part of the overload set resolution rules, it 
would be resolved correctly.

Granted problem two and three are related. But there're subtle 
semantic difference there. Either this has been overlooked or 
there's a reason it is like this and must be like this that I've 
yet to hear.

As it is right now (someone correct me if I'm wrong), for someone 
who is writing libraries, it seems either impractical to use ufcs 
because of compilation errors that it would cause in client code, 
or, worse, unsafe because of silent bugs that would occur inside 
client code AND library code in the case of types adding matching 
function (i.e. same signature/name) in modules that the library 
author and application author have no knowledge, or control of.

Cheers,
- Ali


More information about the Digitalmars-d-learn mailing list