【问题标题】:Why doesn't std::bind account for function arity?为什么 std::bind 不考虑函数数量?
【发布时间】:2014-10-10 11:29:12
【问题描述】:

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

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

如果你想弄乱 哪些 参数被传递以及以什么顺序传递,我可以理解使用占位符,但为什么不只使用默认传递 all 中的参数无需指定的正确顺序?

【问题讨论】:

  • 可能是因为没有办法获取函数的参数数量,否则,这意味着生成的函数对象必须允许 any 数量的参数,包括没有,如果(例如)g 在没有参数的情况下被“调用”,这将导致未定义的行为。
  • 我认为std::placeholders 执行两个功能,1. 正如您所说,它们将参数从源路由到接收器,2. 它们指示需要多少个参数。
  • boost 库可能有更多与bind 相关的原始设计原理。
  • boost::bind 如果你有一个 arity 不匹配,实际上通常不会编译,但它有 bind 的无数重载...你的代码的问题是它与指向的指针匹配-member-data 重载(并且R T::* 也可以匹配指向成员函数的指针,并将 R 推导出为函数类型)。

标签: c++ c++11


【解决方案1】:

但是为什么bind会被设计成这样编译:
auto g = std::bind(&Foo::baz, &foo);
我可以打电话给f,但我不能打电话给g。为什么还要编译?

Boost.Bind FAQ 表示 Boost.Bind 通常会在“绑定时间”诊断此类错误(即在您调用 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) -> foo.bar() 情况无法实现时。

另一种解决方案是继续要求用户明确说明他们想要什么,但提供一种明确的方式来表达“只转发所有内容”,正如 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)

【讨论】:

  • 那么您如何看待将无效绑定表达式诊断为 QoI 增强? :)
  • 好主意!我会调查的:)
  • @NoSenseEtAl,这并没有破坏,这是std::bind 的必需和正确行为。请参阅我的回答中以“首先,由 bind 创建的调用包装器的行为是删除不对应于占位符的参数”开头的段落。请参阅godbolt.org/z/6oj5Pzhb3 了解允许的检查示例以及 GCC 所做的检查。
  • @NoSenseEtAl 因为这是std::bind 的一个有意功能,您可以使用额外的参数调用它,然后它们就会掉在地板上。这种“不匹配”根本没有错。考虑您可以执行b = std::bind(f, _3, _2, _3, _5) 以便b(1, 2, 3, 4, 5) 将调用f(3, 2, 3, 5),而不使用参数14。好吧,您也可以使用更多未使用的参数调用bb(1, 2, 3, 4, 5, 6, 7)
  • 如果您使用错误数量的参数调用std::bind(...),这只是一个错误,因为没有一组参数可以产生有效的调用。如果f 需要四个参数,std::bind(f, _1, _2) 是错误的,因为只有两个参数将传递给f,这是格式错误的。使用额外的参数调用 std::bind 的结果并不是错误。所以回到你的例子,第 14 行是正确的,所以不管你在第 8 行传递多少个参数,this->OnDone() 仍然会被正确调用。
猜你喜欢
  • 2017-04-26
  • 1970-01-01
  • 2011-08-02
  • 2013-11-19
  • 2018-05-14
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多