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

chr*_*ris 1 c++ 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
}
Run Code Online (Sandbox Code Playgroud)

使用默认参数的一个缺点是您必须在列表末尾启动默认参数.有时,这涉及将参数重新排列为一个看起来很愚蠢的订单.要解决这个问题,必须进行单独的重载.

让我拿一个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)
);
Run Code Online (Sandbox Code Playgroud)

要包装它,可能需要默认搜索选项作为标题.有三种理想的实现方法(假设已经使用了其他包装技术).完美的解决方案可能如下:

Window FindWindow (LPCTSTR className = 0, LPCTSTR windowName){...}
Run Code Online (Sandbox Code Playgroud)

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

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

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

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

Joh*_*eek 5

这太荒谬了.有很多角落案例.当前规则非常简单易学:"所有默认参数都必须位于参数列表的末尾." 新规则将是:"省略的默认参数的组合可能不明确,除非必须保留向后兼容性." 更糟糕的是,这甚至不是你可以在定义点测试的规则,因为C++现在甚至都没有为重载函数做到这一点.例如,采用以下两个函数定义:

void foo();
void foo(int x = 0);
Run Code Online (Sandbox Code Playgroud)

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

void foo(int x = 0, int y = 0);
Run Code Online (Sandbox Code Playgroud)

打电话foo(1)怎么办?好吧,为了向后兼容,它必须打电话foo(1, 0).这很有意思,因为这个功能没有这样的困难:

void bar(const char* a = 0, int b = 0);
Run Code Online (Sandbox Code Playgroud)

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

bar("foo");
bar(1);
bar("foo", 1);
bar();
Run Code Online (Sandbox Code Playgroud)

所以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());
Run Code Online (Sandbox Code Playgroud)

现在,生成的版本数量取决于用户定义类型的转换,这些转换甚至可能在函数定义中不可见.在当前版本的C++中,调用baz(B())始终会尝试将B实例转换为a A,否则将失败.现在,有人可能会有理由期待通过您的B实例在第二个参数,这是会发生什么,如果你写了四个重载版本bazbaz(),baz(const A&),baz(const B&),baz(const A&, const B&).baz(B())除非你想破坏现有的代码,否则你甚至不能认为你的default-argument-utopia世界中的调用是模棱两可的.

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

void quux(A* a = nullptr, B* b, C* c = nullptr);
Run Code Online (Sandbox Code Playgroud)

完美明确:作为赎回quux(B*),quux(A*, B*),quux(B*, C*),和quux(A*, B*, C*).除非A继承自(或反之)B继承C.当然,这与重载决策必须面对的问题相同,但到目前为止,默认参数已经完全明确,现在我们陷入了微妙的泥潭.

即使您找到满足每个人的一致解决方案,也几乎不可能简明扼要地解释,这可能是净损失.

  • 其他语言是否具有严格的位置参数,用户定义的转换和预先存在的重载决策规则? (2认同)