STL の関数オブジェクト | Programming Place Plus C++編【標準ライブラリ】 第25章

C++編【標準ライブラリ】 第25章 STL の関数オブジェクト

先頭へ戻る

この章の概要

この章の概要です。


標準の関数オブジェクト

<functional> という標準ヘッダには、よく使うと思われる幾つかの関数オブジェクトが定義されています。

具体的には、以下のものがあります。param1、param2 はそれぞれ、operator() に渡される実引数を表しています。

名称operator() の処理内容
plusparam1 + param2
minusparam1 - param2
multipliesparam1 * param2
dividesparam1 / param2
modulesparam1 % param2
nagate-param1
equal_toparam1 == param2
not_equal_toparam1 != param2
lessparam1 < param2
greaterparam1 > param2
less_equalparam1 <= param2
greater_equalparam1 >= param2
logical_andparam1 && param2
logical_orparam1 || param2
logical_not!param1
bit_andparam1 & param2
bit_orparam1 | param2
bit_xorparam1 ^ param2

例えば、std::sort関数(第22章)は、デフォルトでは昇順にソートを行います。これは、要素の比較を <演算子を使って行っているいるためです。std::sort関数には、比較関数を指定することができるので、ここで std::greater を渡せば、要素の比較を >演算子を使って行うようになるので、降順にソートを行うことができます。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(3);
    v.push_back(2);
    v.push_back(4);
    v.push_back(0);
    v.push_back(1);

    std::sort(v.begin(), v.end(), std::greater<int>());
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

4
3
2
1
0

C++14 (bit_not)

C++14 で、抜けていた「ビット否定」の関数オブジェクト bit_not が追加されました。

名称operator() の処理内容
bit_not~param1


関数アダプタ

C++11 になって大きく変化しているため、C++11 における関数アダプタの項を別途設けています。

関数オブジェクトは、他の関数オブジェクトや関数、値などと組み合わせて使用することが可能です。この考え方は、関数合成と呼ばれ、このテクニックを記述するために、関数アダプタという機能を使用します。関数アダプタ自体は、<functional> という標準ヘッダに定義された関数オブジェクトです。

関数アダプタには以下のものがあります。

名称 処理内容
bind1st bind1st(op, elem) op(elem, param1)
bind2nd bind2nd(op, elem) op(param1, elem)
not1 not1(op) !op(param1)
not2 not2(op) !op(param1, param2)
ptr_fun ptr_fun(op) 通常の関数として op() を呼び出す
mem_fun_ref mem_fun_ref(op) メンバ関数として op() を呼び出す
mem_fun mem_fun(op) オブジェクトのポインタが指すメンバ関数として op() を呼び出す

これだけではよく分からないので、この後1つずつ取り上げていきますが、その前に、関数アダプタを使用する場合に必要な前提について説明しておきます。

関数アダプタは、その実装の都合上、幾つかの型定義を必要としています。つまるところ、関数オブジェクトにするクラスが「公開」された typedef を、標準規格で決められた名称で定義していれば良いのですが、これを簡潔に行うために、2つの構造体が用意されています。この構造体を公開継承(【言語解説】第26章)するだけで準備が整うように設計されています。

具体的には、std::unary_function構造体std::binary_function構造体の2つです。これらは、関数オブジェクトの operator() の引数が1つなのか、2つなのかで使い分けます。

C++11 では、std::unary_function、std::binary_function の使用は非推奨となっています。

unary_function、binary_function は、以下のように定義されています。

namespace std {
    template <typename Arg, typename Result>
    struct unary_function {
        typedef Arg argument_type;
        typedef Result result_type;
    };

    template <typename Arg1, typename Arg2, typename Result>
    struct binary_function {
        typedef Arg1 first_argument_type;
        typedef Arg2 second_argument_type;
        typedef Result result_type;
    };
}

いずれも、テンプレート構造体になっています。テンプレート仮引数には、引数と戻り値の型を指定します。その結果、テンプレート仮引数に当てはめた型に応じた typedef名が定義されます。

これを実際に使うと、次のようになります。

class EvenAndGreaterThanN : public std::unary_function<int, bool> {
public:
    EvenAndGreaterThanN(argument_type N) : mN(N)
    {}

    result_type operator()(argument_type elem) const
    {
        return (elem >= mN) && (elem % 2 == 0);
    }

private:
    const argument_type mN;
};

class WithinRange : public std::binary_function<int, int, bool> {
public:
    WithinRange(first_argument_type min, second_argument_type max) : mMin(min), mMax(max)
    {}

    result_type operator()(first_argument_type elem1, second_argument_type elem2) const
    {
        return mMin <= elem1 && elem2 <= mMax;
    }

private:
    const first_argument_type  mMin;
    const second_argument_type mMax;
};

このようにクラスを定義しておけば、ここから作った関数オブジェクトを関数アダプタに渡すことができるようになります。

bind1st、bind2nd

bind1st関数bind2nd関数 は、関数オブジェクトに渡す引数の個数を2つから1つへ減らすことが目的の関数アダプタです。

例えば、std::replace_if関数(第20章)に渡す関数オブジェクトは、引数が1つだけの単項叙述関数です。ここで、置換対象の条件を「要素の値が 0以下」としたいとき、冒頭で紹介した標準の関数オブジェクトの中から「equal_less」を使いたいところですが、equal_less の処理内容は「param1 <= param2」であり、引数が2つあります。そのため、以下のように使おうとすると、コンパイルエラーになります。

std::replace_if(v.begin(), v.end(), std::less_equal<int>(), 1);

コンパイルエラーになることを別としても、そもそも、「0以下」の「0」の部分を記述する場所が無いことが分かります。ここで、bind2nd を使用すると、問題を解決できます。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

namespace {
    void Println(int elem)
    {
        std::cout << elem << std::endl;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(-2);
    v.push_back(-1);
    v.push_back(0);
    v.push_back(1);
    v.push_back(2);

    std::replace_if(v.begin(), v.end(), std::bind2nd(std::less_equal<int>(), 0), 1);
    std::for_each(v.begin(), v.end(), Println);
}

実行結果

1
1
1
1
2

bind2nd(bind1st も)の第1引数には、引数が2個の関数オブジェクトを指定します。第2引数には、bind2nd の場合は、関数オブジェクトの2個目の引数として渡す値を指定します。(bind1st の場合は、1個目の引数として渡す値を指定します)。

つまり、本来なら引数が1つの関数オブジェクトでなければ受け取ってもらえない箇所(今回の例では replace_if関数に渡す関数オブジェクト)に、引数が2つの関数オブジェクトを渡せるようにする仕組みという訳です。

not1、not2

not1関数not2関数 は、関数オブジェクトが返す結果を反転させます。違いは、関数オブジェクトに渡す引数の個数で、not1 は1つ、not2 は2つの場合に対応しています。

例えば以下のサンプルプログラムでは、「4以上の偶数」という条件を not1 で反転させて使用しています。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

namespace {
    class EvenAndGreaterThanN : public std::unary_function<int, bool> {
    public:
        EvenAndGreaterThanN(argument_type N) : mN(N)
        {}

        result_type operator()(argument_type elem) const
        {
            return (elem >= mN) && (elem % 2 == 0);
        }

    private:
        const argument_type mN;
    };
}

int main()
{
    std::vector<int> v;
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    v.push_back(6);

    std::cout << std::count_if(v.begin(), v.end(), std::not1(EvenAndGreaterThanN(4))) << std::endl;
}

実行結果

3

ptr_fun

ptr_fun関数は、通常の関数を、関数アダプタから使用できるようにするための関数アダプタです。通常の関数は、関数オブジェクトとは違って、std::unary_function や std::binary_function から派生させられないので、その代わりの手段として用意されたものです。

例えば、次のようなコードはコンパイルエラーになってしまいます。

namespace {
    bool IsEven(int elem)
    {
        return elem % 2 == 0;
    }
}

std::count_if(v.begin(), v.end(), std::not1(IsEven));

これは、std::not1関数がその実装の都合上、typedef された型名を必要としているためです。当然、単なる関数である IsEven は、その要求に対応できません。ptr_fun関数を経由させれば、問題を解決できます。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

namespace {
    bool IsEven(int elem)
    {
        return elem % 2 == 0;
    }
}

int main()
{
    std::vector<int> v;
    v.push_back(2);
    v.push_back(3);
    v.push_back(4);
    v.push_back(5);
    v.push_back(6);

    std::cout << std::count_if(v.begin(), v.end(), std::not1(std::ptr_fun(IsEven))) << std::endl;
}

実行結果

2

mem_fun_ref、mem_fun

mem_fun_ref関数mem_fun関数は、オブジェクトのメンバ関数や、オブジェクトを指すポインタからメンバ関数を呼び出すための関数アダプタです。

#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <functional>

namespace {
    class Name {
    public:
        Name(const char* s) : mStr(s)
        {}

        void Print() const
        {
            std::cout << mStr << std::endl;
        }

    private:
        std::string  mStr;
    };
}

int main()
{
    std::vector<Name> v1;
    v1.push_back("xx");
    v1.push_back("xxxxx");
    v1.push_back("xxx");

    std::vector<Name*> v2;
    v2.push_back(&v1[0]);
    v2.push_back(&v1[1]);
    v2.push_back(&v1[2]);

    std::for_each(v1.begin(), v1.end(), std::mem_fun_ref(&Name::Print));
    std::for_each(v2.begin(), v2.end(), std::mem_fun(&Name::Print));
}

実行結果

xx
xxxxx
xxx
xx
xxxxx
xxx

C++11 (関数アダプタ)

C++11 (bind)

C++11 では、std::bind1st、std::bind2nd が非推奨となり、代わる手段として、std::bind が追加されました。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

using namespace std::placeholders;

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

    std::replace_if(std::begin(v), std::end(v), std::bind(std::less_equal<int>(), _1, 0), 1);
    std::for_each(std::begin(v), std::end(v), [](int elem) {std::cout << elem << std::endl; });
}

実行結果

1
1
1
1
2

std::bind は、第1引数に関数や関数オブジェクトを指定し、第2引数以降に引数を指定します。可変個引数になっているので、引数は任意の個数を連続して記述できます。

特定の値にバインドしない引数については、std::placeholders名前空間で定義されている _1、_2、_3・・・といった値を指定します。完全名だと「std::placeholders::_1」のようになりますが、流石に長いので usingディレクティブを用いています。これらの特別な値は、プレースホルダ(置き場)と呼ばれており、std::bind が返した関数オブジェクトに渡す引数を表しています。

初見ではかなり分かりづらいと思うので、簡単な例を挙げます。

#include <algorithm>
#include <iostream>
#include <functional>

using namespace std::placeholders;

namespace {
    void func(int a, int b)
    {
        std::cout << a << ", " << b << std::endl;
    }
}

int main()
{
    auto f1 = std::bind(func, _1, _2);
    f1(3, 5);

    auto f2 = std::bind(func, _2, _1);
    f2(3, 5);

    auto f3 = std::bind(func, _1, 9);
    f3(3);
}

実行結果

3, 5
5, 3
3, 9

std::bind が返すものは平たく言えば関数オブジェクトですが、実際には std::function というクラステンプレートから作られたクラスオブジェクトです。std::function については後の項で紹介しますが、何にせよ、変数で受け取るならば auto を使います。

std::bind が返した関数オブジェクトを呼び出すときに渡した第1引数が _1 のところに、第2引数が _2 のところに来ます。プレースホルダを指定せず、直接「9」という値を指定した f3 の例では、第2引数を省略していますが、ちゃんと 9 が出力されていることが分かると思います。

C++11 (mem_fn)

C++11 では、std::mem_fun_ref、std::mem_fun が非推奨となり、代わる手段として、std::mem_fn が追加されました。

#include <algorithm>
#include <iostream>
#include <vector>
#include <string>
#include <functional>

namespace {
    class Name {
    public:
        Name(const char* s) : mStr(s)
        {}

        void Print() const
        {
            std::cout << mStr << std::endl;
        }

    private:
        std::string  mStr;
    };
}

int main()
{
    std::vector<Name> v1 = { "xx", "xxxxx", "xxx" };
    std::vector<Name*> v2 = { &v1[0], &v1[1], &v1[2] };

    std::for_each(std::begin(v1), std::end(v1), std::mem_fn(&Name::Print));
    std::for_each(std::begin(v2), std::end(v2), std::mem_fn(&Name::Print));
}

実行結果

xx
xxxxx
xxx
xx
xxxxx
xxx

このように、std::mem_fun_ref と std::mem_fun の使い分けは不要になりました。ただ、実際のところ、ラムダ式(【言語解説】第34章)を使って実現できることが多いと思われます。例えば以下のように書き換えても同じ結果を得られます。

int main()
{
    std::vector<Name> v1 = { "xx", "xxxxx", "xxx" };
    std::vector<Name*> v2 = { &v1[0], &v1[1], &v1[2] };

    std::for_each(std::begin(v1), std::end(v1), [](const Name& n) {n.Print(); });
    std::for_each(std::begin(v2), std::end(v2), [](const Name* n) {n->Print(); });
}

C++11 (function)

C++11

C++11 で追加された std::function は、通常の関数、メンバ関数、関数オブジェクトといった、「呼び出しできるもの」を共通で扱えるようにするクラステンプレートです。

#include <algorithm>
#include <iostream>
#include <functional>

using namespace std::placeholders;

namespace {
    void func(int a, int b)
    {
        std::cout << a << ", " << b << std::endl;
    }
}

int main()
{
    std::function<void(int, int)> f = func;

    f(3, 5);

    f = nullptr;

    try {
        f(3, 5);
    }
    catch (const std::bad_function_call& ex) {
        std::cerr << ex.what() << std::endl;
    }
}

実行結果

3, 5
bad function call

std::function のテンプレート仮引数には、戻り値の型(引数の型,...) を指定します。ここで指定した戻り値、引数の組み合わせと一致していれば、通常の関数、メンバ関数、関数オブジェクトといった区別無く代入することができます。代入を行った後は、()演算子によって、代入した関数を呼び出すことができます。

初期化時に引数を与えなかった場合や、後から nullptr を代入した場合には、何も保持していない状態になります。この状態で ()演算子を適用すると、std::bad_function_call例外が送出されます(第17章)。

また、C++11 では、std::unary_function、std::binary_function の使用が非推奨になっており、代わりの手段として、std::function を使います。例えば、std::not1 を使うには、C++03以前では、std::unary_function や std::binary_function から派生したクラスを定義する必要がありましたが、C++11 では以下のように書けます。

#include <algorithm>
#include <iostream>
#include <vector>
#include <functional>

int main()
{
    std::vector<int> v = {2, 3, 4, 5, 6};

    std::cout << std::count_if(
        std::begin(v),
        std::end(v), 
        std::not1(
            std::function<bool(int)>([](int elem){ return (elem >= 4) && (elem % 2 == 0); })
        )
    ) << std::endl;
}

実行結果

3

つまり、ラムダ式によって生成された関数オブジェクトを std::function に渡してしまえば、それで、std::not1 などに適合する関数オブジェクトを得られるという訳です。std::function は、通常の関数でも受け取れるので、ラムダ式を使うことは必須ではありません。


練習問題

問題① 引数が偶数かどうか判定する以下のような IsEven関数があるとして、これを使って、奇数の要素だけを remove_if関数を使って取り除く処理を作成して下さい。

namespace {
    bool IsEven(int elem)
    {
        return elem % 2 == 0;
    }
}


解答ページはこちら

参考リンク



更新履歴

'2018/1/5 コンパイラの対応状況について、対応している場合は明記しない方針にした。

'2017/7/30 clang 3.7 (Xcode 7.3) を、Xcode 8.3.3 に置き換え。

'2017/3/25 VisualC++ 2017 に対応。

'2016/12/7 新規作成。



前の章へ(第24章 数値演算の STLアルゴリズム)

次の章へ(第26章 逆イテレータと挿入イテレータ)

C++編のトップページへ

Programming Place Plus のトップページへ


はてなブックマーク Pocket に保存 Twitter でツイート Twitter をフォロー
Facebook でシェア Google+ で共有 LINE で送る rss1.0 取得ボタン RSS