31

std::bindC++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())
}
4

5 に答える 5

33

これが失敗する理由は、を指定するfoo<Movable>と、バインドする関数が次のようになるためです。

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&&にバインドできないため、これが失敗することがわかりますMovable。†</ p>

代わりに、簡単な解決策は次のようになります。

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

これで、呼び出している関数は次のようになります。

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

そして、呼び出しは正常に機能します(もちろん、必要に応じて呼び出すこともできfoo<const Movable&>ます)。しかし、興味深い質問は、元のバインドを機能させることができるかどうかです。

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&&、ここTで、はテンプレートパラメータですが、それを許可すれば、にでも推測できます。)

于 2011-02-02T06:53:42.340 に答える
2

ラムダ式を使用できます。

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

これが最も簡単なオプションのようです。

于 2011-02-02T11:31:14.097 に答える
1

みんな私はここでバインダーの完璧な転送バージョン(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がない理由がわかりません???

于 2012-11-11T13:08:13.960 に答える
0

(これは実際にはGManの回答に対するコメントですが、コードのフォーマットが必要です)。生成されたファンクターが実際に次のようになっている場合:

your_bindを構築します
{{
    your_bind(Movable arg0):
    arg0(arg0)
    {}

    void operator()()
    {{
        foo(arg0);
    }

    可動arg0;
};

それから

int main()
{{
    auto f = your_bind(Movable());
    f(); //エラーなし!
}

エラーなしで準拠します。rvalueを使用してデータを割り当てて初期化し、foo()のrvalue引数にデータ値を渡すことができるためです。
ただし、バインドの実装では、関数の引数の型がfoo()シグネチャから直接抽出されると思います。つまり、生成されたファンクターは次のとおりです。

your_bindを構築します
{{
    your_bind(Movable && arg0):
    arg0(arg0)// ****エラー:MovableからMovableに変換できません&&
    {}

    void operator()()
    {{
        foo(arg0);
    }

    移動可能&&arg0;
};

実際、これは右辺値データメンバーの初期化に実際に失敗します。おそらく、バインドの実装は、関数の引数型から「参照されていない」型を正しく抽出せず、&&をトリミングせずに、この型をファンクターのデータメンバー宣言に「そのまま」使用します。

正しいファンクターは次のようになります。

your_bindを構築します
{{
    your_bind(Movable && arg0):
    arg0(arg0)
    {}

    void operator()()
    {{
        foo(arg0);
    }

    可動arg0; //トリム&&!!!
};


于 2011-02-02T11:22:04.763 に答える
0

GManNickGの答えのもう1つの改善と私はかなりの解決策を持っています:

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

(gcc-4.9.2およびmsvc2013で動作します)

于 2015-07-15T17:16:33.597 に答える