Reputation: 61920
Hopefully whoever reads this knows about default arguments:
void setCase (string &str, int form = UPPERCASE)
{
for (char &c : str)
c = (form == UPPERCASE ? c & ~0x20 : c | 0x20); //this bit differentiates english uppercase and lowercase letters
}
int main()
{
string s1 = "HeLlO", s2 = s1, s3 = s1;
setCase (s1, UPPERCASE); //now "HELLO"
setCase (s2, LOWERCASE); //now "hello"
setCase (s3); //now "HELLO" due to default argument
}
The one drawback of using default arguments is that you have to start the defaulted arguments at the end of the list. Sometimes this involves rearranging arguments into an order that looks pretty dumb to use. To get around this, separate overloads must be made.
Let me take one Window API function, FindWindow, that finds a window via the class name, title, or both, as an example:
HWND WINAPI FindWindow( //returns handle to window
__in_opt LPCTSTR lpClassName, //param equivalent to const TCHAR *, classes are like templates for windows
__in_opt LPCTSTR lpWindowName //the text that appears on the title bar (for normal windows, for things like buttons, it's what text is on the button)
);
To wrap this, one might want the default search option to be the title. There are three ideal ways of implementing this (assume other wrapping techniques have been used). The perfect solution may very well be as follows:
Window FindWindow (LPCTSTR className = 0, LPCTSTR windowName){...}
A second solution would be to overload one version of the function to just accept the title, and another to accept both. A third would be to switch the order of the parameters.
The main problem with number two is that for longer lists, the amount of space from overloads could get very large as the list grows. The main problem for the third is that anyone who's been using this function beforehand would be used to specifying the class name first. This applies for regular C++ functions as well. Parameters tend to have a natural order.
The main problem with the first solution, of course, is that it is unsupported by the C++ language. My question is:
Is there a possibility of this ever being available in the future?
For example, could compilers just generate proper overloads automatically when needed?
Upvotes: 1
Views: 238
Reputation: 36497
It's ridiculously unlikely. There are a lot of corner cases. The current rule is extremely simple to learn: "all default arguments must come at the end of the argument list." The new rule would be: "no combinations of omitted default arguments may be ambiguous, except where backwards compatibility must be preserved." What's worse, this isn't even a rule you can test at the point of definition, because C++ doesn't even do that right now for overloaded functions. For example, take the following two function definitions:
void foo();
void foo(int x = 0);
Those are perfectly legal even though it's unreasonable to expect the first one ever to get called: anything call that looks like foo()
is ambiguous. Now consider a hypothetical version of C++ where default arguments don't have to come at the end:
void foo(int x = 0, int y = 0);
What does a call to foo(1)
do? Well, to be backwards-compatible, it has to call foo(1, 0)
. That's interesting, because this function has no such difficulties:
void bar(const char* a = 0, int b = 0);
Here are some legit calls to that function:
bar("foo");
bar(1);
bar("foo", 1);
bar();
So the foo
function only generates three versions: foo()
, foo(int)
, and foo(int, int)
. But this one, also with two default arguments, generates four. (And they're not unambiguous: foo(0)
is an ambiguous call.) Well, fine, you can probably work through that with some complicated language in the standard. But now consider this function:
struct A;
struct B;
A some_A();
B some_B();
void baz(const A& a = some_A(), const B& b = some_B());
Now the number of generated versions is dependent on the conversions of your user-defined type, which might not even be visible at the definition of your function. In current versions of C++, a call to baz(B())
would always attempt to convert the B
instance to an A
, and fail otherwise. Now, someone could reasonably expect that to pass your B
instance in the second argument, which is what would happen if you wrote four overloaded versions of baz
as baz()
, baz(const A&)
, baz(const B&)
, baz(const A&, const B&)
. And you can't even consider the call baz(B())
in your default-argument-utopia world to be ambiguous unless you want to break existing code.
Conversions also make even the relatively simple case of "default arguments separated by non-default arguments of different types" kind of messy. For example, this:
void quux(A* a = nullptr, B* b, C* c = nullptr);
Perfectly unambiguous: callable as quux(B*)
, quux(A*, B*)
, quux(B*, C*)
, and quux(A*, B*, C*)
. Unless A
inherits from B
which inherits from C
(or vice versa). Sure, this is the same problem that overload resolution has to face, but so far default arguments have been completely unambiguous, and now we're in a quagmire of subtleties.
Even if you find a consistent solution that satisfies everyone, it'd be almost impossible to explain concisely, which would probably be a net loss.
Upvotes: 5