<functional>でコードのレベルアップ! 【C++のテクニック】
<functional>
は、関数に+αするようなライブラリです。
他にもstd::reference_wrapper
や、std::hash
なども、<functional>
で実装されます。
<functional>
の機能は、便利で、知っておいて損はありません。
C++20時点での仕様を解説をします。
<functional>
はC++11で追加されましたが、バージョンアップにてそこそこ大幅な仕様変更があったので、C++バージョンによってそこそこ違いがあります。
std::function
関数(関数ポインタ)・関数オブジェクト・ラムダ式を同等に扱う C言語で関数を引数に受け取ったり、には、関数ポインタを使用していました。
ただ、関数ポインタだとキャプチャをするラムダ式や関数オブジェクトを受け取ることが出来ないので、そんな時はtemplate
auto
を使用します。
ただ、template
auto
では型が決まっていないため、ヘッダとソースに分けられなかったり、エラーが見づらかったりしていました。
そこで、std::function
を使います。
std::function
は関数(関数ポインタ)、関数オブジェクト、ラムダ式を同じ型のオブジェクトとして扱うことができます。
std::function
のtemplate引数には戻り値型(第1引数の型, 第2引数の型, ...)
の様に指定します。
void func() { }
struct cls {
void operator() { }
};
std::function<void()> f0 = func; // 関数
std::function<void()> f1 = []() { }; // ラムダ式
std::function<void()> f2 = cls{}; // 関数オブジェクト
int a = 10, b = 20;
std::function<int()> getVal = [&a, &b]() // キャプチャ
{
return a + b;
};
std::function
も、ラムダ式や関数のように、関数呼び出し演算子()
で呼び出しできます。
sayHello();
const int val = getVal();
a = 5; b = 40;
const int val2 = getVal();
std::invoke
少し特別な呼び出し 関数呼び出しをする関数です。
これは普段の関数呼び出しにプラスで、INVOKE要件という要件に従った呼び出しが可能です。
std::invoke(sayHello);
std::string str = "Hell";
std::invoke(&std::string::push_back, str, 'o');// str.push_back('o')のようになる
std::pair<int, char> p = { 12345, 123 };
const char p_sec = std::invoke(&std::pair::second, p);// p.secondのようになる
これは、普段の関数呼び出し()
で十分な場合が多かったり、関数がオーバーロードされている場合型指定が必要で少し面倒だったりするので、std::invoke
自体は、あまり使用しないかもしれません。
std::is_invocable
この関数はこの引数で呼び出し可能? std::is_invocable
という、その呼び出し可能オブジェクトがある引数で呼び出し可能かどうかを調べる関数もあります。これは、std::invoke
同様、INVOKE要件に従って呼び出し可能かを調べます。
templateの第1引数には、呼び出し可能なオブジェクトの型を指定し、それ以降に引数のオブジェクトの型を順に指定します。
// std::plus<int>は、引数(int, int)で呼び出し可能かどうか
const bool b = std::is_invocable_v<std::plus<int>, int, int>;
// C言語の可変長引数関数の関数ポインタ
const bool b2 = std::is_invocable_v<int(*)(const char*, ...), const char*, int, char>;
コンストラクタは別に
std::is_constructible
というものが<type_traits>
に定義されています。
引数を事前にセットしておく
std::bind_front
先頭からセット 引数の先頭から順番に束縛します。
std::bind_front
の戻り値は関数オブジェクトなので、std::function
にセットできます。
int f(int a, int b, int c) {
return a + b + c;
}
const auto a = std::bind_front(f, 10, 20);
const std::function<int(int, int)> b = std::bind_front(f, 10);
const std::function<int()> c = std::bind_front(f, 10, 20, 30);
呼び出すときは束縛した引数を省略し、普通の関数呼び出しのように呼び出します。
a(30);
b(20, 30);
c();
std::bind
好きな引数をセット 任意の引数を束縛します。
std::bind
の戻り値も関数オブジェクトなので、std::function
にセットできます。
束縛しない引数にはプレースホルダーを使用します。
プレースホルダーは、呼び出しの時の引数の番号を指定します。
int f(int a, int b, int c) {
return a + b + c;
}
// プレースホルダー[_1, _2, ...]が宣言されている名前空間
using namespace std::placeholders;
const auto a = std::bind(f, 10, _1, 30);
const std::function<int(int, int)> b = std::bind(f, _1, _2, 30);
const std::function<int()> c = std::bind(f, 10, 20, 30);
事前にセットした引数を省略して呼び出します。
a(20);
b(10, 30);
c();
std::mem_fn
std::mem_fn
は、メゾッド単体から呼び出し可能オブジェクトを生成します。
INVOKE要件的には、std::bind_front
で代替ができます。
struct cls {
void mthd() { }
int mthd2(int a, int b) { return a + b; }
};
const auto a = std::mem_fn(&cls::mthd);
const std::function<int(int, int)> b = std::mem_fn(&cls::mthd2);
const std::function<int(int, int)> c = std::bind_front(&cls::mthd2);// bind_frontでもOK
オブジェクトを第一引数にセットします。
cls obj;
a(obj);
b(obj, 10, 20);
c(obj, 10, 20);
std::mem_fn
の便利な使い方 メゾッドを関数の様に扱う
std::mem_fn
の戻り値を更にstd::bind_front
することで、メゾッドを関数と同等に扱うことが出来ます。革命的!!
void callFunc(const std::function<void()>& f) {
f();
}
struct cls {
void mthd() { std::cout << __FUNCTION__ "\n"; }
};
cls obj;
const auto func = std::bind_front(std::mem_fn(&cls::mthd), obj);
callFunc(func);
その他の機能
std::reference_wrapper
参照オブジェクト std::reference_wrapper
は、普通の&
の参照、そしてポインタに変わる、参照オブジェクトです。
std::reference_wrapper
は、普通の参照&
では出来なかった、参照先の変更が出来ます。
注意点として、普通に参照先の値に代入をしたい場合はget()
などを使用して参照先にアクセスする必要があります。
int a, b;
std::reference_wrapper<int> rw = a;
rw = b;// bを参照するように切り替える
// rw = 100; これは出来ない
rw.get() = 100;
参照をコンテナに入れたい時にも使用します。
std::vector<std::reference_wrapper<int>> refs;
int a, b, c;
refs.push_back(a);
refs.push_back(b);
refs.push_back(c);
std::ref
std::cref
std::ref
std::cref
を使用してstd::reference_wrapper
オブジェクトを生成します。
template引数に参照を渡すときなどに使用します。
template引数が1つの場合<>
を使用して参照を指定すればいいですが、可変引数templateのように、いっぱいtemplate引数がある場合に優位に使えます。
template<class... Args>
void f(Args&&... args) { }
int a;
f<int, int, int&>(10, 20, a);// これだと引数を変更するときに<>内も変更しないといけない
f(10, 20, std::ref(a));
std::hash
ハッシュ値を求める std::unordered_map
などで使用されるハッシュ値を求める関数オブジェクトです。
戻り値はsize_t
型です。
const auto i_h = std::hash<int>()(100);
const auto s_h = std::hash<std::string>()("abc");
演算子の関数オブジェクト
std::plus
std::minus
std::multiplies
std::divides
など、各演算子の関数オブジェクトが定義されています。
まとめ
std::function
を使用して関数(関数ポインタ)・関数オブジェクト・ラムダ式を同等のオブジェクトとして扱うことができます。
std::bind
std::bind_front
を使用して、任意の引数を事前にセットをすることができます。
std::mem_fn
を使用してメゾッドを普通の関数同等に扱うことが出来ます。
その他、<functional>
には便利な関数オブジェクトがあります。
今後の開発の参考になれば幸いです。
ありがとうございました。
Discussion