1. ホーム
  2. c++

[解決済み] C++でcopy_ifなどの「単項述語」を定義するにはどうしたらいいですか?

2022-02-18 23:47:22

質問

std::copy_if()を使おうとしているのですが、その構文がどのように動作するかは、以下の記事からなんとなくわかりました。 http://www.cplusplus.com/reference/algorithm/copy_if/ :

auto it = std::copy_if (foo.begin(), foo.end(), bar.begin(), [](int i){return !(i<0);} );

最後の引数は、私が混乱しているものです。括弧は何のためにあるのでしょうか?私がどこかで書いた関数を引数として使うことはできるのでしょうか、そしてそれはどのように機能するのでしょうか?関数に渡す変数を指定すれば、別の引数を渡すことができるのでしょうか?

全体的な質問としては、これらの構文がどこにあるのか、ということですね。この例を使って、いくつかの本当に簡単なことを宣言することができますが、私はそれを使ってより多くのことができるようにしたいと思います。単項述語が何をすべきで何をすべきでないかを説明する場所はいくつか見つけましたが、実際にどのように宣言すればいいのか、それが何を意味するのかについてはわかりません。私はまだc++のアルゴリズムに慣れていないので、より効果的な使い方を学びたいと思っています。

どのように解決するのですか?

関数のように振る舞うものなら何でも、述語として copy_if . よく使われるものがいくつかあります。

1) 関数

関数は確かに関数のように振る舞うので、述語として copy_if :

bool is_less_than_zero(int i) { return i < 0; }

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b), is_less_than_zero);
    // now b will contain the elements {-2, -1}
}

ライブデモ

2)オーバーロードされたオブジェクト operator()

オブジェクトはオーバーロードすることができます operator() というように、関数のように動作します。 これらはしばしば関数オブジェクト、またはファンクタと呼ばれます。 これにより、生の関数では実現できない状態を保存することができます。

struct IsLessThan {
    IsLessThan(int i) : i_{i} {}
    bool operator()(int i) { return i < i_; }
    int i_;
};

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b), IsLessThan(0));
    // now b will contain the elements {-2, -1}
}

ライブデモ

3) ラムダ

ラムダは概念的には無名関数です。 実際には、オブジェクトの構文上の糖分として、オーバーロードされた operator() しかし、そのおかげで、少ないコードで簡単な述語を作成するのに便利なツールとなっています。

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b),
                 [](int i){ return i < 0; });
    // now b will contain the elements {-2, -1}
}

ライブデモ

ラムダは実際にはオブジェクトで、オーバーロードされた operator() また、ラムダのキャプチャーリストで指定された状態を含むことができます。

int main() {
    std::vector<int> a = {1, 2, -2, -1};
    std::vector<int> b;
    int number_to_compare_to = 0;

    std::copy_if(a.begin(), a.end(), std::back_inserter(b),
                 [number_to_compare_to](int i){ return i < number_to_compare_to; });
    // now b will contain the elements {-2, -1}
}

ライブデモ

標準ライブラリには、状態を含む関数オブジェクトを簡単に作成し、それを使って関数にパラメータの一部を供給する機能があります (すなわち std::bind しかし、ほとんどの場合、ラムダを使う方が簡単です。 つまり、次のコードは、どちらも全く同じ動作をする2つのオブジェクトを作成します。

bool first_less_than_second(int i, int j) { return i < j; }

int main() {
    auto bind_less_than_zero = std::bind(first_less_than_second, std::placeholders::_1, 0);
    auto lambda_less_than_zero = [](int i){ return first_less_than_second(i, 0); };
}

一般的には、ラムダバージョンの方が好ましいのですが、それでも時々 std::bind (またはc++11以前のブースト対応版である boost::bind )が採用されています。