web-dev-qa-db-ja.com

C ++ 14でラムダに対してstd :: bindを使用する理由

C++ 11以前は、boost::bindまたはboost::lambdaをたくさん使用していました。 bind部分は標準ライブラリ(std::bind)になり、他の部分はコア言語(C++ラムダ)の一部になり、ラムダの使用がはるかに簡単になりました。現在、私はほとんどstd::bindを使用していません。C++ラムダでほとんど何でもできるからです。考えられるstd::bindの有効なユースケースが1つあります。

struct foo
{
  template < typename A, typename B >
  void operator()(A a, B b)
  {
    cout << a << ' ' << b;
  }
};

auto f = bind(foo(), _1, _2);
f( "test", 1.2f ); // will print "test 1.2"

それに相当するC++ 14は

auto f = []( auto a, auto b ){ cout << a << ' ' << b; }
f( "test", 1.2f ); // will print "test 1.2"

はるかに短く、簡潔です。 (C++ 11では、自動パラメーターのため、これはまだ機能しません。)std::bindがC++ラムダの代替手段を破る他の有効な使用例はありますか、またはstd::bindはC++ 14で不要ですか?

55
Ralph Tandetzky

スコット・マイヤーズは、これについて talk を出しました。これは私が覚えていることです:

C++ 14には、ラムダでも実行できない便利なバインドはありません。

C++11では、ラムダではできないことがいくつかあります。

  1. ラムダの作成時にキャプチャ中に変数を移動することはできません。変数は常に左辺値としてキャプチャされます。バインドの場合、次のように記述できます。

    auto f1 = std::bind(f, 42, _1, std::move(v));
    
  2. 式はキャプチャできず、識別子のみをキャプチャできます。バインドの場合、次のように記述できます。

    auto f1 = std::bind(f, 42, _1, a + b);
    
  3. 関数オブジェクトの引数のオーバーロード。これはすでに質問で言及されています。

  4. 完全前進の議論は不可能

C++14では、これらすべてが可能です。

  1. 移動の例:

    auto f1 = [v = std::move(v)](auto arg) { f(42, arg, std::move(v)); };
    
  2. 式の例:

    auto f1 = [sum = a + b](auto arg) { f(42, arg, sum); };
    
  3. 質問を見る

  4. 完璧な転送:あなたは書くことができます

    auto f1 = [=](auto&& arg) { f(42, std::forward<decltype(arg)>(arg)); };
    

バインドのいくつかの欠点:

  • バインドは名前でバインドします。その結果、同じ名前の複数の関数(オーバーロードされた関数)がある場合、バインドはどちらを使用するかを認識しません。次の例はコンパイルされませんが、ラムダは問題ありません。

    void f(int); void f(char); auto f1 = std::bind(f, _1, 42);
    
  • バインド関数を使用すると、インライン化される可能性が低くなります

一方、ラムダはバインドよりも多くのテンプレートコードを理論的に生成する可能性があります。ラムダごとに一意の型を取得するためです。バインドの場合は、異なる引数タイプと異なる関数がある場合のみです(ただし、実際には同じ引数と関数で何度もバインドすることはあまりないでしょう)。

Jonathan Wakelyが彼の答えで言及したことは、実際にはバインドを使用しないもう1つの理由です。引数を黙って無視したい理由がわかりません。

62
BertR

std::bindは、ポリモーフィックラムダができないことの1つをまだ実行できます。オーバーロードされた関数を呼び出す

struct F {
  bool operator()(char, int);
  std::string operator()(char, char);
};

auto f = std::bind(F(), 'a', std::placeholders::_1);
bool b = f(1);
std::string s = f('b');

バインド式によって作成された呼び出しラッパーは、指定した引数に応じて異なる関数を呼び出します。C++ 14ポリモーフィックラムダからのクロージャーは、異なるtypesの引数が異なるnumberの引数を取ることはできず、クロージャで常に同じ関数(の特殊化)を呼び出します。 修正:以下のコメントを参照

std::bindによって返されるラッパーは、too many引数で呼び出すこともできますが、それらは無視されますが、ラムダによって作成されたクロージャーは診断しますあまりにも多くの引数を渡そうとします...しかし、私はstd::bindの利点とは考えていません:)

29
Jonathan Wakely

時々、コードが少なくなります。このことを考慮:

bool check(int arg1, int arg2, int arg3)
{
  return ....;
}

それから

wait(std::bind(check,a,b,c));

vsラムダ

wait([&](){return check(a,b,c);});

https://en.wikipedia.org/wiki/Brainfuck のように見えるラムダと比較して、ここではバインドが読みやすいと思います

2
AlexTheo

私にとっては、std::bindは、メンバー関数を述語として使用していることを明確にすることです。つまり、メンバー関数を呼び出すだけであれば、バインドされます。引数を(メンバー関数を呼び出す以外に)追加する場合、ラムダです。

using namespace std;
auto is_empty = bind(&string::empty, placeholders::_1); // bind = just map member
vector<string> strings;
auto first_empty = any_of(strings.begin(), strings.end(), is_empty);

auto print_non_empty = [](const string& s) {            // lambda = more than member
    if(s.empty())                // more than calling empty
        std::cout << "[EMPTY]";  // more than calling empty
    else                         // more than calling empty
        std::cout << s;          // more than calling empty
};
vector<string> strings;
for_each(strings.begin(), strings.end(), print_non_empty);
2
utnapistim

@BertRのコメントを この回答 にテスト可能なものに展開するだけですが、std :: forward <>を使用して解決策を得ることができなかったと認めています。

#include <string>
#include <functional>
using namespace std::string_literals;

struct F {
    bool        operator()(char c, int  i) { return c == i;  };
    std::string operator()(char c, char d) { return ""s + d; };
};

void test() {
    { // using std::bind
        auto f = std::bind(F(), 'a', std::placeholders::_1);
        auto b = f(1);
        auto s = f('b');
    }
    { // using lambda with parameter pack
        auto x = [](auto... args) { return F()('a', args...); };
        auto b = x(1);
        auto s = x('b');
    }
}

Compiler Explorer でテスト

0
Orwellophile

もう1つの違いは、バインドする引数はコピーまたは移動する必要がありますが、ラムダは参照によってキャプチャされた変数を使用できることです。以下の例を参照してください。

_#include <iostream>
#include <memory>

void p(const int& i) {
    std::cout << i << '\n';
}

int main()
{
    std::unique_ptr<int> f = std::make_unique<int>(3);

    // Direct
    p(*f);

    // Lambda ( ownership of f can stay in main )
    auto lp = [&f](){p(*f);};
    lp();

    // Bind ( does not compile - the arguments to bind are copied or moved)
    auto bp = std::bind(p, *f, std::placeholders::_1);
    bp();
}
_

void p(const int&)の署名を変更せずに上記のバインドを使用することで問題を回避できるかどうかはわかりません。

0
Zitrax