コンテンツにスキップ

C++/ジェネリックラムダ

出典: フリー教科書『ウィキブックス(Wikibooks)』
< C++

ジェネリックラムダの概要

[編集]

C++11で導入されたラムダ式は、無名の関数オブジェクトを簡潔に記述する機能です。C++14では、これをさらに拡張してジェネリックラムダが導入されました。

ジェネリックラムダは、パラメータの型を自動的に推論し、異なる型に対して汎用的な関数を作成できる機能です。これにより、テンプレート関数のような汎用コードをより簡潔に記述できます。ジェネリックプログラミングは異なる型に同じ処理を適用することが主な目的です。ジェネリックラムダはその理念を具現化し、コードの重複を避けて抽象化を高める手段を提供します。

auto lambda = [](auto x, auto y) { return x + y; };
std::cout << lambda(1, 2.3) << std::endl; // 3.3
std::cout << lambda(4, 5) << std::endl;   // 9

上記の例では、autoキーワードによってパラメータ型が推論され、整数や浮動小数点数など様々な型の組み合わせで同じラムダ式を使用できます。

ジェネリックラムダの構文

[編集]

パラメータ型の推論

[編集]

ジェネリックラムダは、autoキーワードでパラメータ型を推論します。

[](auto x, auto y) { /* 処理 */ }

上記の構文では、autoを付けたパラメータの型は、呼び出し時の実引数から自動的に決まります。

戻り値型の推論

[編集]

C++14では、ラムダ式の戻り値型も推論できます。-> autoを使うことで、戻り値型が式から推論されます。

auto lambda = [](auto x, auto y) -> auto { return x + y; };

この場合、xyの型に基づいて戻り値型が決まります。

可変引数の扱い

[編集]

ジェネリックラムダは可変長引数をサポートし、任意の数の引数を受け取ることができます。

auto lambda = [](auto&&... args) {
    // 可変長引数 args を処理
};

例えば、フォールディング式を用いることで、可変長引数の合計を計算することができます。

auto sum = [](auto&&... args) {
    return (args + ... + 0); // 引数の合計値を返す
};

std::cout << sum(1, 2.3, 4, 5.6) << std::endl; // 12.9

(args + ... + 0)は二項フォールディング式で、引数が1つもなかった場合への対策として+ 0が付加されています。

実例によるジェネリックラムダの活用

[編集]

ジェネリックラムダは、簡単な計算から複雑な処理まで幅広く活用できます。

簡単な数値計算

[編集]
auto add = [](auto x, auto y) { return x + y; };
std::cout << add(1, 2.3) << std::endl; // 3.3
std::cout << add(4, 5) << std::endl; // 9

コンテナアルゴリズムでの利用

[編集]

STLアルゴリズムと組み合わせることで、ジェネリックラムダを有効に活用できます。

std::vector<int> nums = {1, 5, 3, 7, 2};
int sum = std::accumulate(nums.begin(), nums.end(), 0, [](int x, int y) { return x + y; });

ジェネリックラムダとテンプレートの使い分け

[編集]

ジェネリックラムダは一種のテンプレートプログラミングですが、通常の関数テンプレートと異なり、コンパクトな記述を可能にします。ジェネリックラムダは小規模な処理に適し、より複雑なジェネリックコードには関数テンプレートを使用するのが推奨されます。

関数オブジェクトとしてのジェネリックラムダ

[編集]

ジェネリックラムダは、関数オブジェクトの代替として使われることが多く、特に短く記述したい場合や柔軟な型推論を活用したい場合に便利です。

std::functionとの連携

[編集]

ジェネリックラムダはstd::function型と組み合わせて使用することができます。ただし、パフォーマンスの観点からは、std::functionによる間接呼び出しはオーバーヘッドが発生する可能性があるため、性能が重要な場面では注意が必要です。

std::function<int(int)> func = [](auto x) { return x * 2; };
std::cout << func(10) << std::endl; // 20

関数オブジェクトの生成と利用

[編集]

ラムダ式を用いることで、短いコードで関数オブジェクトを作成できます。これにより、コードの可読性が向上し、手軽にカスタム関数オブジェクトを実装できます。

auto multiplier = [](auto x, auto factor) { return x * factor; };
std::cout << multiplier(5, 3.2) << std::endl; // 16.0

ジェネリックラムダの制限とベストプラクティス

[編集]

ジェネリックラムダは型推論を活用できるため非常に便利ですが、いくつかの制約と注意点があります。

  • 複雑な型制約: ジェネリックラムダ内でテンプレートパラメータの詳細な制約を記述することはできません。必要な場合は関数テンプレートを使用することが推奨されます。
  • コンパイラエラーメッセージ: 型推論に依存することで、コンパイラのエラーメッセージがわかりにくくなることがあります。複雑なコードを作成する際は、型を明示的に指定するなどの対策が必要です。
まとめ

ジェネリックラムダは、コードの簡潔さと型推論の柔軟性を兼ね備えた非常に強力なツールです。特に、C++14以降の標準ライブラリやテンプレートメタプログラミングと組み合わせることで、汎用的で読みやすいコードを記述できます。