参数列表中任意点的默认参数是否可能
Will default arguments at an arbitrary point in the argument list ever be possible?
希望任何阅读本文的人都知道默认参数:
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
(反之亦然(。当然,这是重载解决必须面对的相同问题,但到目前为止,默认参数是完全明确的,现在我们陷入了微妙的泥潭。
即使你找到一个让每个人都满意的一致解决方案,也几乎不可能简洁地解释,这可能是一个净损失。
- 在C++中,使用带有 std::optional 参数的函数<T>来表示可选参数是否有意义?
- 如何检查给定的参数是否为 cv::noArray()?
- 如果返回 -1,时间() 的参数是否被修改?
- C++中大多数/所有 setter 函数的参数是否应该写为常量引用?
- 检查两个模板参数是否相同
- 空函数的参数是否加载到缓存中?
- 使用 lambda 作为构造函数参数是否需要C++ 17?
- 了解'this'或其他参数是否为右值
- const-ref传递的模板化参数是否经过优化,以便在足够小时按值传递
- shared_ptr构造函数参数是否应按值传递
- 如何检查模板参数是否为给定值?
- 使用聚合初始化模拟默认函数参数是否存在任何陷阱?
- 在对象序列化期间添加额外参数是否有更好的方法?
- 通过 ssh 发送参数.是否有非阻塞输入函数?
- 如何检查运算符 != 模板参数是否存在 C++ 17?
- 常量引用函数参数:是否可以禁止临时对象?
- 如何检查模板参数是否为 std::variant?
- 是否可以确定函数的参数是否已签名或无符号,以实现可能性超载函数
- 移动 l 值参考参数是否是一种不好的做法?
- 显式指定通用 lambda 的 operator() 模板参数是否合法?