【问题标题】:What the heck does std::bind(x, y) do?std::bind(x, y) 到底是做什么的?
【发布时间】:2011-07-01 05:06:20
【问题描述】:

我一直在阅读 N3225 中 std::bind 的描述,位于 20.8.10.1 小节中。它说下面应该打印1,但我认为bind应该复制它的参数,因此它应该打印0。如果要引用传递的参数,则需要使用std::ref,对吗?

void f(int &a) { a = 1; }

int main() {
  int a = 0;
  std::bind(f, a)();
  std::cout << a << std::endl;
}

GCC 输出0,与我认为可行的一致。但是 N3225 说std::bind(f, a1) 应该返回一个调用包装器,当被wrapper() 调用时将调用INVOKE(f, v1),其中v1 应该是a(我传入的参数,换句话说,使用binds' s 传入参数,这是一个完美的转发参数,std::forward&lt;A1&gt;(a1))。

INVOKE(f, a) 由 20.8.2 定义为 f(a)。因此,这定义了对返回的调用包装器的调用传递了原始参数。我错过了什么?

【问题讨论】:

标签: c++ c++11 stdbind


【解决方案1】:

它说下面应该打印 1

不,它没有这么说。

如果要引用传递的参数,则需要使用 std::ref ,对吗?

是的。

但是 N3225 说 std::bind(f, a1) 应该返回一个调用包装器,当被 wrapper() 调用时将调用 INVOKE(f, v1),其中 v1 应该是 a(我传入的参数,在换句话说,使用绑定的传入参数,它是一个完美的转发参数,std::forward(a1))。

那就是你错了。您在绑定调用中传入的“绑定参数”以TiD 类型的新创建对象的形式存储,每个对象构造 来自forward&lt;Ti&gt;(ti)。这可以通过说“tid 是来自std::forward&lt;Ti&gt;(ti) 的 TiD constructed 类型的左值”来相当清楚地说明。由于参考包装器的特殊处理,有一个额外的“转换层”。请参阅 20.8.10.1.2/10,其中解释了 viVitidTiD 的关系。

【讨论】:

  • 对我来说,说TiD 的左值是从forward&lt;Ti&gt;(ti) 构造的,这意味着forward&lt;Ti&gt;(ti) 应该是一个左值,或者它应该被转换为TiD&amp;,如(TiD&amp;)forward&lt;Ti&gt;(ti) 到是一个左值。我无法将其解读为它将创建一个左值引用的新对象。因为在这种情况下,左值将通过命名该新对象来构造。 IMO 这是一种非常令人困惑的表述方式。
  • “构造”意味着“对象”。在(TiD&amp;)forward&lt;Ti&gt;(ti) 中没有发生对象构造。左值引用不是对象类型。
  • “constructed”可以表示创建对象以外的其他内容。与“根据语法规则构建的 C++ 程序 [...]”(“格式良好”的定义)进行比较。 “构造”与“构造左值”一起使用并不意味着创建对象。构造一个左值只能意味着它所说的,而不是其他的。下一个代码从标识符“x”构造一个左值:int main() { int x = 0; x; }。在接下来的代码中,没有构造左值 int main() { int x = 0; }.
  • 当然,您可以在其他方面更松散地使用“constructed”。例如,我在谈论在类型特征类is_constructible&lt;int,int&gt; 中使用的“构造”。如果您认为草案需要在该领域进行澄清,请提出更新。我个人认为本节完全足够。
  • @sellibitze is_constructible&lt;int,int&gt; 在 20.7.4.3p6 的描述看起来非常合适。它没有说“如果 T 左值可以从 U 左值构造为真”之类的话。但它是用一个临时变量来表达的。
【解决方案2】:

目前它打印一个 0,因为在您调用 std::bind 时它不知道您要传递引用。它不会查看函数的签名来查看它采用哪些参数类型并进行相应调整。

为了让它正常工作调用

void f(int &a) { a = 1; }

int main() {
  int a = 0;
  std::bind(f, std::ref(a))();
  std::cout << a << std::endl;
}

C++0x 建议使用“完美绑定”,但这样做存在巨大的危险,它可能会严重破坏依赖当前行为的现有代码。这是一个非常简单的例子。

void myCallback( const std::string& str, int i );

function< void(int) > makeCallback( const std::string & str )
{
    return bind( myCallback, str, _1 );
}

目前,您可以依赖绑定复制您使用str 传入的字符串,因此在回调调用时它将是有效的。

如果它“聪明地”使用“完美绑定”来存储它作为参考,它会破坏这样的情况。

【讨论】:

  • 原发帖人明确表示他知道使用std::ref。他询问应该如何解释规范,不是如何使代码打印 1,所以这不是一个答案。
  • 我现在已经回答了为什么不强制显式 std::ref 可能非常危险。不确定它是否直接回答了问题,我真的不明白所有这些 tid 的东西......
【解决方案3】:

哇,这令人难以置信。它将v1 定义为tid,如下所示(ti 是第 i 个完美转发绑定参数,TiD 是该参数的衰减类型 - 即数组变成指针等)。

tid 是由std::forward&lt;Ti&gt;(ti) 构造的TiD 类型的左值

好吧,我确实说过,这个tidstd::forward&lt;Ti&gt;(ti),它是一个左值!但这并不是它真正要说的意思。这意味着

tidTiD 类型的左值,它引用从 std::forward&lt;Ti&gt;(ti) 构造的对象

现在更有意义了。因为如果std::forward&lt;Ti&gt;(ti) 实际上是一个右值呢? “lvalue ...从...构造”意味着我们从“...”创建一个新对象并让左值引用它。

【讨论】:

    猜你喜欢
    • 2021-07-15
    • 2018-02-14
    • 2022-06-15
    • 1970-01-01
    • 2014-12-13
    • 1970-01-01
    • 1970-01-01
    • 2016-06-11
    • 2015-08-06
    相关资源
    最近更新 更多