【问题标题】:Passing rvalues through std::bind通过 std::bind 传递右值
【发布时间】:2011-06-19 18:25:00
【问题描述】:

我想通过std::bind 将右值传递给在 C++0x 中采用右值引用的函数。我不知道该怎么做。例如:

#include <utility>
#include <functional>

template<class Type>
void foo(Type &&value)
{
    Type new_object = std::forward<Type>(value);    // move-construct if possible
}

class Movable
{
public:
    Movable(Movable &&) = default;
    Movable &operator=(Movable &&) = default;
};

int main()
{
    auto f = std::bind(foo<Movable>, Movable());
    f();    // error, but want the same effect as foo(Movable())
}

【问题讨论】:

    标签: c++ c++11 std rvalue-reference rvalue


    【解决方案1】:

    失败的原因是当你指定foo&lt;Movable&gt;时,你绑定的函数是:

    void foo(Movable&&) // *must* be an rvalue
    {
    }
    

    但是,std::bind 传递的值将不是右值,而是左值(作为成员存储在生成的 bind 函子中的某处)。也就是说,生成的函子类似于:

    struct your_bind
    {
        your_bind(Movable arg0) :
        arg0(arg0)
        {}
    
        void operator()()
        {
            foo<int>(arg0); // lvalue!
        }
    
        Movable arg0;
    };
    

    构造为your_bind(Movable())。所以你可以看到这失败了,因为Movable&amp;&amp; 不能绑定到Movable。†

    一个简单的解决方案可能是这样的:

    auto f = std::bind(foo<Movable&>, Movable());
    

    因为现在你调用的函数是:

    void foo(Movable& /* conceptually, this was Movable& &&
                            and collapsed to Movable& */)
    {
    }
    

    而且通话效果很好(当然,如果需要,您可以拨打foo&lt;const Movable&amp;&gt;)。但一个有趣的问题是,我们是否可以让您原来的绑定工作,我们可以通过:

    auto f = std::bind(foo<Movable>,
                std::bind(static_cast<Movable&&(&)(Movable&)>(std::move<Movable&>),
                    Movable()));
    

    也就是说,我们只是在调用之前std::move 参数,所以它可以绑定。但是,哎呀,这很丑陋。强制转换是必需的,因为std::move 是一个重载函数,所以我们必须通过强制转换为所需的类型来指定哪个我们想要的重载,消除其他选项。

    如果std::move 没有被重载,它实际上不会那么糟糕,就好像我们有类似的东西:

    Movable&& my_special_move(Movable& x)
    {
        return std::move(x);
    }
    
    
    auto f = std::bind(foo<Movable>, std::bind(my_special_move, Movable()));
    

    这要简单得多。但除非你有这样的函数,否则我认为你可能只是想指定一个更明确的模板参数。


    † 这与在没有显式模板参数的情况下调用函数不同,因为显式指定它消除了推导它的可能性。 (T&amp;&amp;,其中T是模板参数,可以推导出为anythingif you let it be。)

    【讨论】:

    • 谢谢。我确实在尝试做后者(将右值作为右值传递给 foo 以启用移动语义)。不过,我希望像std::ref 这样的右值引用;您最后一个解决方案的绑定仍然有点太长,并且嵌套绑定会分散注意力。
    • @Timothy:拥有一个右值int 真的没那么有用,它仍然只是一个int。您正在使用更复杂的类型吗?如果是这样,也许您可​​以编辑您的问题以明确您的期望。 (如果是这样的话,我认为移动语义的规范用法可能存在误解;别担心,它肯定还没有广为人知!)
    • @GMan 但是难道不能实现 std::bind ,如果它本身有右值,它在内部使用 std::forward 来传递右值吗?这不是 std::bind 实现或规范中的错误(它仍然是草稿,因此报告错误是有道理的,对吧?)
    • @Jan:我也很惊讶,但阅读草稿似乎只是将它作为左值传递。查看 §20.8.10.1.2/1,我们在其中看到 tid(用于说明)的定义为“是一个左值......由 std::forward(ti) 构造”。根据第 10 段,函数被传递,“值 tid 及其类型 Vi 是 TiD cv &”。换句话说,存储为通过转发参数构造的成员,并仅作为普通值传递。但我可以看到这个选择背后的理由,而且我也认为不需要转发该值,如果@Tim 详细说明我可能会涉及其他解决方案。
    • @Tim:我明白了。好吧,我同意不幸的是,没有用于移动绑定值的简单实用程序,但是绑定到允许它的foo&lt;Movable&amp;&gt; 不会有任何损失。请注意,您也可能走完全不同的路线:auto f = []{ foo(Movable()); }
    【解决方案2】:

    您可以使用 lambda 表达式。

    auto f = [](){ foo(Movable()); };
    

    这似乎是最简单的选择。

    【讨论】:

    • 对不起,我把你的答案记下来了......原因如下:虽然这会起作用,但绑定通常用于以后回忆原始上下文......它在原始问题中的使用方式是与 lambda 的闭包部分 [...] 相同。相反,您的解决方案假定您可以推迟创建 Movable 直到您准备好调用 foo.哪种方式首先忽略了绑定它的要点。如果你能做到这一点,为什么还要把 Movable 对象作为参数放在首位,为什么不直接在 foo 中构造它呢?
    • 他在原问题中明确表示想要foo(Movable())的效果。
    • 这是恕我直言,迄今为止最简单和最好的答案。当然,必须确保 lambda 表达式是安全的,特别是,如果它移出某个值,则它通过引用捕获并且不会在适当位置产生值。但是,而不是 std::binding 到 std::bind 的结果(如在接受的答案中),更容易和更清楚地忽略发生了什么。
    【解决方案3】:

    伙计们,我在这里破解了一个完美的活页夹转发版本(限制为 1 个参数) http://code-slim-jim.blogspot.jp/2012/11/stdbind-not-compatable-with-stdmove.html

    代码供参考

    template <typename P>
    class MovableBinder1
    {
      typedef void (*F)(P&&);
    
    private:
      F func_;
      P p0_;
    
    public:
      MovableBinder1(F func, P&& p) :
        func_(func),
        p0_(std::forward<P>(p))
      {
        std::cout << "Moved" << p0_ << "\n";
      }
    
      MovableBinder1(F func, P& p) :
        func_(func),
        p0_(p)
      {
        std::cout << "Copied" << p0_ << "\n";
      }
    
      ~MovableBinder1()
      {
        std::cout << "~MovableBinder1\n";
      }
    
      void operator()()
      {
        (*func_)(std::forward<P>(p0_));
      }
    };
    

    从上面的概念证明中你可以看到,它很有可能......

    我看不出为什么 std::bind 与 std::move 不兼容... std::forward 毕竟是完美转发我不明白为什么没有 std::forwarding_bind ???

    【讨论】:

    【解决方案4】:

    (这实际上是对 GMan 答案的评论,但我需要对代码进行一些格式化)。 如果生成的函子实际上是这样的:

    结构 your_bind { your_bind(可移动 arg0): arg0(arg0) {} 无效运算符()() { 富(arg0); } 可移动的 arg0; };

    然后

    主函数() { 自动 f = your_bind(Movable()); F(); // 没有错误! }

    编译没有错误。因为可以使用右值分配和初始化数据,然后将数据值传递给 foo() 的右值参数。
    但是,我认为绑定实现直接从 foo() 签名中提取函数参数类型。即生成的函子是:

    结构 your_bind { your_bind(可移动 && arg0) : arg0(arg0) // **** 错误:无法从 Movable 转换为 Movable &&amp {} 无效运算符()() { 富(arg0); } 可动&&arg0; };

    确实,这确实无法初始化右值数据成员。 也许,绑定实现根本没有正确地从函数参数类型中提取“未引用”类型,而是“按原样”使用该类型作为仿函数的数据成员声明,​​而不修剪 &&。

    正确的函子应该是:

    结构 your_bind { your_bind(Movable&& arg0) : arg0(arg0) {} 无效运算符()() { 富(arg0); } 可移动的 arg0; // 修剪 && !!! };

    【讨论】:

      【解决方案5】:

      GManNickG 的回答又多了一个改进,我有了很好的解决方案:

      auto f = std::bind(
          foo<Movable>,
          std::bind(std::move<Movable&>, Movable())
      );
      

      (适用于 gcc-4.9.2 和 msvc2013)

      【讨论】:

        猜你喜欢
        • 2018-08-29
        • 2016-04-24
        • 2017-11-26
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-06-03
        • 1970-01-01
        相关资源
        最近更新 更多