参数列表中任意点的默认参数是否可能

Will default arguments at an arbitrary point in the argument list ever be possible?

本文关键字:参数 是否 默认 列表 任意点      更新时间:2023-10-16

希望任何阅读本文的人都知道默认参数:

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++语言不支持它。我的问题是:
将来是否有可能提供此功能?

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

不太可能。有很多极端情况。当前的规则非常容易学习:"所有默认参数都必须位于参数列表的末尾。新规则将是:"省略的默认参数的任何组合都不能模棱两可,除非必须保持向后兼容性。更糟糕的是,这甚至不是您可以在定义时测试的规则,因为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实例,这就是如果您将四个重载版本的baz编写为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继承自继承自C B(反之亦然(。当然,这是重载解决必须面对的相同问题,但到目前为止,默认参数是完全明确的,现在我们陷入了微妙的泥潭。

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