1

希望读到这里的人都知道默认参数:

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
}

使用默认参数的一个缺点是您必须在列表末尾开始默认参数。有时这涉及将参数重新排列成一个看起来很愚蠢的顺序。为了解决这个问题,必须进行单独的重载。

让我以一个 Window API 函数FindWindow为例,它通过类名、标题或两者来查找窗口:

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)
);

为了包装这一点,人们可能希望默认搜索选项是标题。有三种理想的实现方式(假设使用了其他包装技术)。完美的解决方案很可能如下:

Window FindWindow (LPCTSTR className = 0, LPCTSTR windowName){...}

第二种解决方案是重载该函数的一个版本以仅接受标题,而另一个版本则接受两者。第三个是切换参数的顺序。

第二个主要问题是对于更长的列表,随着列表的增长,重载的空间量可能会变得非常大。第三个的主要问题是任何事先使用过这个函数的人都会习惯于首先指定类名。这也适用于常规 C++ 函数。参数往往具有自然顺序。

当然,第一个解决方案的主要问题是它不受 C++ 语言的支持。我的问题是:
将来是否有可能实现这一点?

例如,编译器能否在需要时自动生成适当的重载?

4

1 回答 1

5

这不太可能。有很多极端案例。当前规则非常容易学习:“所有默认参数都必须位于参数列表的末尾。” 新规则将是:“省略的默认参数的组合不得有歧义,除非必须保留向后兼容性。” 更糟糕的是,这甚至不是您可以在定义时测试的规则,因为 C++ 现在甚至不会为重载函数执行此操作。例如,取以下两个函数定义:

void foo();
void foo(int x = 0);

这些都是完全合法的,即使期望第一个被调用是不合理的:任何看起来像的调用foo()都是模棱两可的。现在考虑一个假设的 C++ 版本,其中默认参数不必放在最后:

void foo(int x = 0, int y = 0);

来电foo(1)做什么?好吧,要向后兼容,它必须调用foo(1, 0). 这很有趣,因为这个函数没有这样的困难:

void bar(const char* a = 0, int b = 0);

以下是对该函数的一些合法调用:

bar("foo");
bar(1);
bar("foo", 1);
bar();

所以该foo函数只生成三个版本:foo()foo(int)foo(int, int)。但是这个也有两个默认参数,生成四个。(而且它们并不是明确的:foo(0)是一个模棱两可的调用。)好吧,您可以使用标准中的一些复杂语言来解决这个问题。但现在考虑这个函数:

struct A;
struct B;
A some_A();
B some_B();
void baz(const A& a = some_A(), const B& b = some_B());

现在生成版本的数量取决于您的用户定义类型的转换,这甚至可能在您的函数定义中不可见。在当前版本的 C++ 中,调用baz(B())总是会尝试将B实例转换为A,否则会失败。现在,有人可以合理地期望B在第二个参数中传递您的实例,如果您编写bazas baz()baz(const A&)baz(const B&)、的四个重载版本会发生这种情况baz(const A&, const B&)baz(B())除非您想破坏现有代码,否则您甚至不能认为默认参数乌托邦世界中的调用是模棱两可的。

转换甚至使“默认参数由不同类型的非默认参数分隔”的相对简单的情况变得混乱。例如,这个:

void quux(A* a = nullptr, B* b, C* c = nullptr);

完全明确:可调用为quux(B*), quux(A*, B*), quux(B*, C*), 和quux(A*, B*, C*). 除非A继承自B哪个继承自C(反之亦然)。当然,这与重载解析必须面对的问题相同,但到目前为止,默认参数已经完全明确,现在我们陷入了微妙的泥潭。

即使你找到了一个让每个人都满意的一致解决方案,也几乎不可能简明扼要地解释清楚,这可能是一个净损失。

于 2012-04-05T03:19:16.350 回答