C++の第一級関数/第一級オブジェクト/関数オブジェクト

C++ではラムダ式や、ファンクタ(関数オブジェクト)の定義とそのインスタンスの生成を用いることで、第一級オブジェクト(ファーストクラスオブジェクト)を実現します。ラムダ式や、ファンクタによって生成されたオブジェクトは、第一級関数(ファーストクラスファンクション)として扱うことが可能となります。

ファンクタ

ファンクタは関数オブジェクトを表現するためのものです。

struct Print {
   template<class T> void operator()(const T& v) const {
      std::cout << v << std::endl;
   }
};
Print print = Print();
print("Shop");   // "Shop\n"
Print p = print;
p("99");         // "99\n"

auto print_ = Print{};
std::vector<int> v = {1, 2, 3};
std::for_each(v.begin(), v.end(), print); // "1\n2\n3\n"
std::for_each(v.begin(), v.end(), print_);
std::for_each(v.begin(), v.end(), Print{});
参考: 色々な関数オブジェクト #ファンクタ

ラムダ式

ファンクタの宣言とインスタンス化が面倒な場合は、auto オブジェクト名 = ラムダ式形式で関数オブジェクトを直接定義することも可能です。ラムダ式はファンクタを簡易的に定義するための糖衣構文として利用することができます。

int main() {
   auto print = [](const auto& v) {
      std::cout << v << std::endl;
   };
   
   print("Shop"); // "Shop\n"
   std::vector<int> v = {9, 9};
   std::for_each(v.begin(), v.end(), print); // "9\n9\n"
}
参考: 色々な関数オブジェクト #ラムダ式

ラムダ式で宣言されたprintクロージャオブジェクトは一般的な関数として振る舞うこともできますし、第一級関数として変数に格納したり、他の関数の引数や戻り値としてやり取りすることもできます。

またラムダ式は無名関数でもあるため、第一級オブジェクトとして任意の場所で定義したり利用することも可能です。

std::vector<int> v = {9, 9};
std::for_each(v.begin(), v.end(), [](const auto& v) {
   std::cout << v << std::endl;
});

豆知識として、ファンクタやラムダ式による関数オブジェクトは、main関数の外や特定の名前空間内で変数宣言することも可能です。

auto print = [](const auto& v) {
   std::cout << v << std::endl;
};

int main() {
   print("Shop");
}
広告