【问题标题】:Will default arguments at an arbitrary point in the argument list ever be possible?参数列表中任意点的默认参数是否可能?
【发布时间】:2012-04-05 02:56:31
【问题描述】:

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

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

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

【问题讨论】:

  • 不喜欢?使用不同的语言,例如 Python,它使用 keyword argumentssubprocess.Popen 就是一个很好的例子。
  • 我可以忍受。我只是想知道(可能还有许多其他人也是如此)这是否会随着新的 C++ 添加进入该语言而改变。我确实记得另一种语言(可能是 Python)很适合参数传递,因为它能够判断参数是默认的还是用户只是传递了默认值。
  • 仍然可以计算出哪些列表是明确的,对于(int a = 0, int b = 1) 之类的情况,请坚持当前的从右到左的约定。像(string a, int b = 0, int c = 1, string d) 这样的东西肯定会在传递一个 int 时模棱两可。甚至可以通过首选表达式的类型来解决类似 double/int 组合的问题。 (string a, int b = 0, string c, int d = 1, string e) 仍然非常明确。一开始可能需要学习一些额外的东西,但我认为它会奏效。
  • 我想指出,有时“命名参数习语”(C++ 常见问题解答)很有帮助。 只是有时,我不喜欢把它放在任何地方。但是你可以命名参数,这很酷。

标签: c++ default-arguments


【解决方案1】:

这不太可能。有很多 很多 极端案例。当前规则非常容易学习:“所有默认参数都必须位于参数列表的末尾。”新规则将是:“省略的默认参数的组合不得有歧义,除非必须保留向后兼容性。”更糟糕的是,这甚至不是您可以在定义时测试的规则,因为 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&),就会发生这种情况.除非您想破坏现有代码,否则您甚至不能认为在您的 default-argument-utopia 世界中调用 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(反之亦然)。当然,这与重载解析必须面对的问题相同,但到目前为止,默认参数已经完全明确,现在我们陷入了微妙的泥潭。

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

【讨论】:

  • 您的帖子有一些优点。真正困扰我的是其他语言确实做到了。我同意这种添加的必要更改会不必要地大,并且对文件大小也没有真正的帮助。
  • 其他具有严格位置参数、用户定义的转换和预先存在的重载解析规则的语言可以做到吗?
  • C++ 必须以某种方式与众不同:)
  • @chris:你有支持这种处理位置参数方式的语言示例吗?
  • @NiklasB。从我收集到的信息来看,Python 似乎很好地完成了这类事情,但它更多的是名称-价值而不是严格的位置。我不知道任何具有我描述的语法的语言,我只是想知道随着 C++ 的发展,我是否可以期待这样的东西。
猜你喜欢
  • 2017-05-11
  • 2011-08-04
  • 1970-01-01
  • 2014-09-29
  • 2014-07-30
  • 1970-01-01
  • 1970-01-01
  • 2023-01-11
  • 1970-01-01
相关资源
最近更新 更多