21 Feb, 2009, Silenus wrote in the 1st comment:
Votes: 0
Hi,

I am curious if anyone knows how C++ handles, resolves at compile time the overloading of functions of the same name but which take different arguments and what the shortcomings of whatever the C++ people did was. How does C++ determine what is the best match in cases when the arguments pattern match correctly against multiple signatures? I assume some method needs to exist to cope with unifying the argument types against the formal list and some scoring function would need to exist to assess which particular match is best.

Thanks in advance,

Silenus.
21 Feb, 2009, kiasyn wrote in the 2nd comment:
Votes: 0
I don't think you can ever have a conflict? Other than with inheritance, in which case i would assume the deepest class would win.

ie int func( Child &c ) would win over int func( Base &c )
21 Feb, 2009, Silenus wrote in the 3rd comment:
Votes: 0
How about something like:

int func( Child &c, Base &c) vs. int func( Base &c, Child &c)?
22 Feb, 2009, David Haley wrote in the 4th comment:
Votes: 0
It's pretty straightforward. The closest match always wins. If several matches are equally close, it complains at you about an ambiguity.

Example:

$ cat test.cpp

#include <iostream>
#include <string>

class Super
{
public:
const char* s;
};
class Sub : public Super
{
};

void f(Super& a, Sub& b)
{
std::cout << "called with: " << a.s << ", " << b.s << std::endl;
}
void f(Sub& a, Super& b)
{
std::cout << "called with: " << a.s << ", " << b.s << std::endl;
}

void g(Super& a)
{
std::cout << "g(Super) is called" << std::endl;
}
void g(Sub& a)
{
std::cout << "g(Sub) is called" << std::endl;
}

int main(char ** argv, int argc)
{
Super sup; sup.s = "super";
Sub sub; sub.s = "sub";

f(sup, sub);
f(sub, sup);
f(sub, sub);

g(sup);
g(sub);
}

$ g++ test.cpp
test.cpp: In function 'int main(char**, int)':
test.cpp:39: error: call of overloaded 'f(Sub&, Sub&)' is ambiguous
test.cpp:14: note: candidates are: void f(Super&, Sub&)
test.cpp:18: note: void f(Sub&, Super&)


Removing the offending call (the third call to f)

$ g++ test.cpp
$ ./a.out
called with: super, sub
called with: sub, super
g(Super) is called
g(Sub) is called


You can play around with deeper inheritance chains too and see what it does. Basically, it prefers exact matches, and will complain about ambiguity.
22 Feb, 2009, Silenus wrote in the 5th comment:
Votes: 0
How is close defined in this case? i.e. suppose we extend the cases out to 3 arguments where F subtypes E subtypes D subtypes C subtypes B subtypes A.

void F(F&,A&,F&)

void F(E&,E&,E&)

is the second considered closer than the first or the first over the second assuming you call with F(F,F,F)? Or in the general case where you have signatures like:

T_return F(T_0&, T_1&, … T_N&)

a subtyping relationship and a some arbitrary call?
22 Feb, 2009, David Haley wrote in the 6th comment:
Votes: 0
In the case of F,F,F, it is simple because the exact match is preferred. I don't know what the exact rule is, formally defined. I don't believe that its notion of "closeness" has anything to do with inheritance depth, but rather the number of casts (or constructor calls) required. Fortunately, it is very easy to play around with these general cases in very small C++ source files.
22 Feb, 2009, Tyche wrote in the 7th comment:
Votes: 0
See section 13.2 of the C++ standard/
Here's an older draft copy: http://www.csci.csusb.edu/dick/c++std/cd...
22 Feb, 2009, David Haley wrote in the 8th comment:
Votes: 0
Section 13.3, actually (at least in that draft): "overload resolution".
22 Feb, 2009, Silenus wrote in the 9th comment:
Votes: 0
Thanks.

Wow. I guess looking at that for inspiration might not be a good thing. I guess they do construct a total ordering of some sort of the form always prefer A over B over C. I am also not sure for what I am doing it would work either. A set of rules that run over quite a few pages just to explain the behavior seems a little complex and with the system I have it might get worse :/. I guess I will just not permit it for now and perhaps add it later if I can think of a sensible set of rules which doesn't take 6-10 pages to explain.
22 Feb, 2009, David Haley wrote in the 10th comment:
Votes: 0
Silenus said:
A set of rules that run over quite a few pages just to explain the behavior seems a little complex

Well, there are a lot of cases to cover, right? :wink: Don't forget that C++ is a very complex language with lots of stuff happening: templates, member functions, static member functions, operator overloading, and so forth. A few pages actually seems a little short to me. It's not simple behavior, after all, and involves quite a lot of "magic" if you're not aware it's happening. It's possible that your case is quite simpler.

The very basic notion of a simple overloading system can be expressed quickly, though. Minimize the number of casts or copy constructors, and always prefer exact matches. If two candidates involve the same number of casts, punt and declare ambiguity. (Don't try to be clever with inheritance depth.)
22 Feb, 2009, Silenus wrote in the 11th comment:
Votes: 0
Well I have heard complaints about C++ being overly complex in general and this might be an example of it. The problem is that what the C++ people have done really doesn't generalize easily. I might just sidestep the issue for now since once you pile on a ML like type system I am not sure what kind of monstrosity would be the result if I also introduced overriding though I would like to be able to define functions which are something as follows:

filter<T>( [T], (T) -> bool ) -> [T] // [] denotes an array
filter<K,V>( {K:V}, (K,V) -> bool ) -> {K:V} // {K:V} denotes a map

Obviously I could use different names for the functions filter_array and filter_map. I suspect scoring/ranking matches in cases where you have type parameters spanning over multiple arguments might be a bit tricky since in a sense you are reducing the dimensionality of the original problem and thus it can be argued that you should consider the "restricted" dimension count over the actual dimension count. With sum/union types, recursive types the choice I suspect becomes even less clear.
22 Feb, 2009, David Haley wrote in the 12th comment:
Votes: 0
Silenus said:
The problem is that what the C++ people have done really doesn't generalize easily

Why not?

Silenus said:
filter<T>( [T], (T) -> bool ) -> [T] // [] denotes an array
filter<K,V>( {K:V}, (K,V) -> bool ) -> {K:V} // {K:V} denotes a map

In this case, it is very easy to pick which function to use based on the input types.
22 Feb, 2009, Silenus wrote in the 13th comment:
Votes: 0
Anyhow it isn't clear to me at all how you would do it if you had type nesting + subtyping + union types + recursive typing (as the bottom part of my post explained ;-) ). I said I would like to have those two functions but obviously i cannot predict how ppl will override functions with the same name in general so any system would have to cope with in a reasonable manner any function (general case) that someone could dream up under a more ML like type system.
22 Feb, 2009, David Haley wrote in the 14th comment:
Votes: 0
I still think your example is simple. Just punt if somebody calls it with something that could go either way. If it's unambiguous, which will be the case in the vast majority of instances I predict, and you're happy, or it's ambiguous and you refuse it. Just because you have all these crazy types doesn't mean you have to fully support the whole shebang in every single feature.
23 Feb, 2009, Silenus wrote in the 15th comment:
Votes: 0
Thanks for the reply.

Oh the example is simple to treat but it's the general problem that seems to me a bit harder (perhaps I lack the proper training to fully understand it at present). I was thinking of a simple modified type unify scheme & discard if ambiguous (modified since I would have to account for implicit conversions/promotions and handle normal argument lists versus ellipsis). This would be a "simple" system.

From a formal perspective I wonder if I need to go through a few more books on type theory and semantics of programming languages before attempting this pass of the compiler. I suspect I could write it now but I am uncertain (given how limited my skills are in this area) of whether I would do a particularly good job with it.
23 Feb, 2009, Tyche wrote in the 16th comment:
Votes: 0
You might want to attempt something easier first, like say a compiler for LPC.
23 Feb, 2009, Silenus wrote in the 17th comment:
Votes: 0
The complexities are pretty much isolated in the type system so I am not too worried. I do have a book on compiling functional languages which should have clues concerning how to handle the situation with transparent types in principle. The problem is primarily speed. All this additional type checking at runtime = alot of extra work.

LPC is easier in some respects (or I can just throw out my extended type system) but also messier- FluffOS because of it's long history has multiple ways of doing similar things. If I extend the type system of LPC as I plan to the problems are similar so I figure I might as well just bite the bullet and try to resolve these type issues in a proper way all at once.

Most likelly preliminary versions will crash anyhow on the more complex type cases and I will just implement code for handling int, float and strings.
0.0/17