如果我有这个简单的案例:
struct Foo
{
void bar();
void baz(int );
};
Run Code Online (Sandbox Code Playgroud)
这有意义,这将编译:
Foo foo;
auto f = std::bind(&Foo::bar, &foo);
Run Code Online (Sandbox Code Playgroud)
但是为什么会bind以这样的方式设计:
auto g = std::bind(&Foo::baz, &foo);
Run Code Online (Sandbox Code Playgroud)
我可以打电话f,但我不能打电话g.为什么要编译呢?要求我做的背后的理由是什么:
auto g2 = std::bind(&Foo::baz, &foo, std::placeholders::_1);
Run Code Online (Sandbox Code Playgroud)
我可以理解使用占位符,如果你想弄乱哪些参数传递和按什么顺序,但为什么不只是让默认传递所有参数的顺序而不必指定它?
\n\n但是为什么 bind 会以这样的方式设计:
\n
\nauto g = std::bind(&Foo::baz, &foo);
\n我可以调用f,但我永远不能调用g。为什么还要编译呢?
Boost.Bind FAQ说Boost.Bind 通常会在“绑定时间”(即在您调用的线路上bind)诊断此类错误。然而,标准并不要求 for ,而是在Requires元素std::bind中包含以下内容:std::bind
\n\n\n
INVOKE (fd, w1, w2, ..., wN)(20.9.2) 应是某些值w1, w2, ..., wN的有效表达式,其中N == sizeof...(bound_args).
这意味着您的代码违反了函数的前提条件,从而导致未定义的行为。标准库实现没有义务检查是否违反前提条件,这是您的工作。库也不被禁止检查它们,因此它会符合拒绝它的实现,就像 Boost.Bind 所做的那样。我会向您的库供应商提出请求,要求他们在可能的情况下诊断无效的绑定表达式,作为“实施质量”的增强。bind(编辑:从 GCC 5 开始,我让 libstdc++执行此操作。)
\n\n为什么不让默认值以正确的顺序传递所有参数而不必指定它呢?
\n
我可以想到两个原因。
\n首先,创建的调用包装器的行为bind是删除与占位符不对应的参数,因此您可以调用x(1, 2, 3)并让它忽略所有参数并调用foo.bar(). 这是一般模式的一部分,您可以使用 N 元数函数来包装 N 元数函数,以创建具有完全不同元数的调用包装器,该包装器可能会添加参数、删除它们、将某些参数修复为特定的绑定值bind等。x(1, 2, 3)所有参数(如果绑定表达式中未使用占位符时的默认行为是转发所有参数)。
其次,始终要求您明确要按什么顺序传递哪些参数会更加一致。一般来说,只有在没有绑定参数时传递所有调用参数才有意义,否则如何bind知道是在绑定参数之前还是之后传递调用参数?
例如给定
\nstruct X {\n void f(int, int) { }\n} x;\nauto h = bind(&X::f, &x, 1);\nh(2);\nRun Code Online (Sandbox Code Playgroud)\n调用应该h(2)导致x.f(1, 2)or 吗x.f(2, 1)?由于存在绑定参数时的正确行为并不明显,因此在没有使用占位符时自动转发所有参数只有在没有绑定参数时才真正有意义(因为这样就不存在绑定参数是否应该首先出现的问题或最后),这是一个相当特殊的情况。改变 API 的一个重要特性来处理这种特殊情况的价值是值得怀疑的,特别是当它使得x(1, 2, 3)->foo.bar()情况无法实现时。
另一种解决方案是继续要求用户明确他们想要什么,但提供一种明确的方式来表达“只转发所有内容”,正如 Tomasz Kami\xc5\x84ski 在N4171中提出的那样,该方案将在接下来的 C++ 委员会会议上讨论星期。占位_all符解决了决定调用参数应该出现在绑定参数之前还是之后的问题,因为您可以明确地说出您想要bind(f, arg1, arg2, std::placeholders::_all)或bind(f, std::placeholders::_all, arg1, arg2)什至bind(f, arg1, std::placeholders::_all, arg2)
| 归档时间: |
|
| 查看次数: |
600 次 |
| 最近记录: |