如果我有这个简单的案例:
struct Foo
{
void bar();
void baz(int );
};
这可以编译:
Foo foo;
auto f = std::bind(&Foo::bar, &foo);
但为什么bind
会以这样的方式设计:
auto g = std::bind(&Foo::baz, &foo);
我可以致电f
,但我不能打电话给g
。为什么要编译呢?要求我做的背后的理由是什么:
auto g2 = std::bind(&Foo::baz, &foo, std::placeholders::_1);
我可以理解使用占位符,如果你想弄乱哪些参数被传递以及以什么顺序传递,但为什么不只是默认传递所有参数正确的顺序而不必指定它?
答案 0 :(得分:3)
但是为什么绑定的设计方式应该是这样编译:
auto g = std::bind(&Foo::baz, &foo);
我可以致电f
,但我不能打电话给g
。为什么要编译呢?
Boost.Bind FAQ表示Boost.Bind通常会在“绑定时间”(即在您调用bind
的行上)诊断此类错误。但是,该标准不要求std::bind
,而是std::bind
的 Requires 元素中包含以下内容:
INVOKE (fd, w1, w2, ..., wN)
(20.9.2)应该是某些值 w1,w2,...,wN 的有效表达式,其中N == sizeof...(bound_args)
。
这意味着您的代码违反了函数的前提条件,从而导致未定义的行为。标准库实现没有义务检查先决条件违规,这是你的工作。该库也不被禁止检查它们,因此它将符合实现拒绝它,如Boost.Bind所做的那样。我会向您的图书馆供应商发出请求,要求他们在可能的情况下诊断无效的绑定表达式,作为“实施质量”增强。
为什么不在正确的顺序中默认传递所有参数而不必指定它?
我可以想到两个原因。
首先,bind
创建的调用包装器的行为是删除与占位符不对应的参数,因此可以调用x(1, 2, 3)
并让它忽略所有参数并调用{{ 1}}。这是一般模式的一部分,你可以使用foo.bar()
包装一个N-arity函数来创建一个带有完全不同的arity的调用包装器,它可以添加参数,删除它们,修复一些特定的绑定值等等。如果在绑定表达式中没有使用占位符时的默认行为是转发所有参数,则不可能bind
删除所有参数。
其次,总是要求您明确要求哪些参数以哪种顺序传递,这更加一致。一般来说,只有在没有绑定参数时传递所有调用参数才有意义,否则x(1, 2, 3)
应该如何知道是否在绑定参数之前或之后传递调用参数?
e.g。给定
bind
struct X {
void f(int, int) { }
} x;
auto h = bind(&X::f, &x, 1);
h(2);
的来电是h(2)
还是x.f(1, 2)
?由于存在绑定参数时的正确行为并不明显,因此当没有使用占位符时自动转发所有参数只有在没有绑定参数时才真正有意义(因为那时不存在绑定参数应该是第一个还是最后一个的问题) ,这是一个相当特殊的案例。更改API的重要功能以使用该特殊情况将具有可疑价值,尤其是当它使x.f(2, 1)
- > x(1, 2, 3)
案件无法实现。
另一种解决方案是继续要求用户明确他们想要的内容,但提供一种明确的方式来表达“只是转发一切”,正如TomaszKamiński在N4171中提出的那样,将在C ++中讨论下周的委员会会议。 foo.bar()
占位符解决了决定调用参数是应该在绑定参数之前还是之后的问题,因为您可以明确说出是否需要_all
或bind(f, arg1, arg2, std::placeholders::_all)
甚至{{1} }