为什么 std::bind 不考虑函数 arity?

Why doesn't std::bind account for function arity?

本文关键字:arity 函数 bind std 为什么 不考虑      更新时间:2023-10-16

如果我有这个简单的例子:

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);

如果你想扰乱哪些参数以什么顺序传递,我可以理解使用占位符,但为什么不让默认值以正确的顺序传递所有的参数而不必指定呢?

但是,为什么要以这样一种方式设计绑定,以便编译:
auto g = std::bind(&Foo::baz, &foo);
我可以呼叫f,但我永远不能呼叫g。为什么还要编译它?

Boost.Bind常见问题解答说Boost.BBind通常会在";绑定时间";(即在您呼叫bind的线路上)。然而,该标准对std::bind没有要求,相反,它在std::bindRequires元素中有以下内容:

INVOKE (fd, w1, w2, ..., wN)(20.9.2)应该是一些值w1,w2,…的有效表达式。。。,wN,其中N == sizeof...(bound_args)

这意味着您的代码违反了函数的前提条件,从而导致未定义的行为。标准库实现没有义务检查是否违反了先决条件,这是您的工作。库也不被禁止检查它们,所以实现拒绝它是符合要求的,就像Boost.Bind所做的那样。我会向您的库供应商提出请求,要求他们在可能的情况下诊断无效绑定表达式;实施质量";增强。(编辑:我让libstdc++的bind这样做,从GCC 5开始。)

为什么不让默认值按正确的顺序传递所有参数而不必指定它呢?

我能想到两个原因。

首先,bind创建的调用包装器的行为是删除与占位符不对应的参数,因此可以调用x(1, 2, 3)并让它忽略所有参数并调用foo.bar()。这是一个通用模式的一部分,在该模式中,您可以使用bind包装一个N-arity函数,以创建一个具有完全不同arity的调用包装器,该调用包装器可以添加参数、删除参数、将某些参数修复为特定的绑定值等。如果绑定表达式中不使用占位符时的默认行为是转发所有参数,则x(1, 2, 3)不可能删除所有参数。

其次,总是要求您明确表示要以何种顺序传递哪些参数更为一致。通常,只有在没有绑定参数的情况下才传递所有调用参数才有意义,否则bind应该如何知道是在绑定参数之前还是之后传递调用参数?

例如给定

struct X {
  void f(int, int) { }
} x;
auto h = bind(&X::f, &x, 1);
h(2);

h(2)的调用应该产生x.f(1, 2)还是x.f(2, 1)?由于有绑定参数时的正确行为并不明显,因此只有在没有绑定参数时才有意义(因为绑定参数不应该排在第一位还是最后一位),这是一种相当特殊的情况。改变API的一个重要特性来处理这种特殊情况将是值得怀疑的价值,特别是当它使x(1, 2, 3)->CCD_ 23的情况不可能实现。

另一种解决方案是继续要求用户明确他们想要什么,但提供一种明确的方式来表示";只是转发一切";,这是Tomasz Kamiński在N4171中提出的,将在下周的C++委员会会议上讨论。_all占位符解决了决定调用参数应该在绑定参数之前还是之后的问题,因为您可以明确地说您想要bind(f, arg1, arg2, std::placeholders::_all)还是bind(f, std::placeholders::_all, arg1, arg2),甚至bind(f, arg1, std::placeholders::_all, arg2)