グローバルラムダを使用しない理由はありますか?


89

それ自体の内部にある非キャプチャラムダを使用する関数がありました。例:

void foo() {
  auto bar = [](int a, int b){ return a + b; }

  // code using bar(x,y) a bunch of times
}

ラムダによって実装された機能が別の場所で必要になったので、ラムダをfoo()グローバル/名前空間スコープに持ち上げます。ラムダのままにするか、コピーと貼り付けのオプションにするか、適切な関数に変更できます。

auto bar = [](int a, int b){ return a + b; } // option 1
int bar(int a, int b){ return a + b; } // option 2

void foo() {
  // code using bar(x,y) a bunch of times
}

それを適切な関数に変更するのは簡単ですが、ラムダのままにしない理由があるのだろうかと思いました。「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?


ラムダを注意深く使用しないと、意図しない変数をキャプチャすると多くのエラーが発生すると思います
マクロ

回答:


61

グローバルラムダを使用しない非常に重要な理由が1つあります。それは正常ではないためです。

C ++の通常の関数構文は、Cの時代から存在しています。プログラマーは何十年もの間、構文の意味とその機能を知っていました(ただし、関数からポインターへの減衰は、経験豊富なプログラマーであっても時々噛み付くことがあります)。「まったくの初心者」を超えるスキルレベルのC ++プログラマが関数の定義を見ると、彼らは何を手に入れているのかを知っています。

グローバルラムダは、まったく別の獣です。通常の関数とは動作が異なります。ラムダはオブジェクトですが、関数はそうではありません。それらには型がありますが、その型は機能の型とは異なります。などなど。

これで、他のプログラマーとのコミュニケーションの水準が上がりました。C ++プログラマーは、この関数が何をしているかを理解しようとする場合、ラムダを理解する必要があります。そして、はい、これは2019年なので、まともなC ++プログラマーはラムダがどのように見えるかを理解している必要があります。しかし、それはまだより高いバーです。

そして、たとえ彼らがそれを理解したとしても、そのプログラマーの心の問題は...なぜこのコードの作成者はそれをそのように書いたのでしょうか?また、その質問に対する適切な回答がない場合(たとえば、範囲のカスタマイズポイントのように、オーバーロードを明示的に禁止したい場合)、共通のメカニズムを使用する必要があります。

必要に応じて、予想されるソリューションを新規のソリューションよりも優先します。最も簡単な方法でポイントを伝えます。


9
「Cの日以来」その前の私の友人
オービットのライトネスレース

4
@LightnessRaces:私の主な目的は、C ++の関数構文がC以降に存在していることを表現することでした。
Nicol

2
最初の文を除いて、この回答すべてに同意します。「それは正常ではない」という漠然とした曖昧な発言。「それは珍しくて混乱している」という意味でそれを意味したのでしょうか?
einpoklum

1
@einpoklum:C ++には、「一般的ではなく混乱する」と見なされる可能性のある多くのものがあり、ドメイン内ではまだ「正常」です(詳細なテンプレートメタプログラミングを参照)。この場合、「通常」は完全に有効です。これは、歴史的にも今日も、C ++プログラミング経験の「規範」の範囲内にあるものではありません。
Nicol Bolas

@NicolBolas:しかし、その意味では、それは循環的な推論です。OPは、まれな方法を採用すべきかどうか疑問に思っています。まれな慣行は、ほとんどが定義による「標準」ではありません。しかし、nm。
einpoklum

53

グローバルラムダを通常の関数のドロップイン置換として避けたいいくつかの理由を考えることができます。

  • 通常の関数はオーバーロードできます。ラムダはできません(ただし、これをシミュレートする手法はあります)
  • それらは関数のようであるという事実にもかかわらず、このような非キャプチャラムダでさえメモリを占有します(通常、非キャプチャ用に1バイト)。
    • コメントで指摘されているように、最新のコンパイラはas-ifルールの下でこのストレージを最適化します

「ステートフルファンクタ(クラス)を置き換えるためにラムダを使用すべきではないのはなぜですか?」

  • クラスはラムダよりも制限が少ないため、最初に到達する必要があります
    • (パブリック/プライベートデータ、オーバーロード、ヘルパーメソッドなど)
  • ラムダに状態がある場合、いつそれがグローバルになるかについて推論するのはさらに困難です。
    • 可能な限り狭いスコープでクラスのインスタンスを作成することをお勧めします
  • 非キャプチャラムダを関数ポインターに変換することはすでに困難であり、そのキャプチャで何かを指定するラムダは不可能です。
    • クラスは、関数ポインターを作成する簡単な方法を提供します。また、クラスは多くのプログラマーにとってより快適なものです
  • キャプチャー付きのラムダはデフォルトで作成できません(C ++ 20の場合。以前は、デフォルトのコンストラクターはありませんでした)。

ラムダへの暗黙の "this"ポインター(キャプチャされていないものであっても)もあり、ラムダを呼び出す場合と関数を呼び出す場合に追加のパラメーターになります。
1201ProgramAlarm

@ 1201ProgramAlarm:それは良い点です。ラムダの関数ポインターを取得するのははるかに難しい場合があります(キャプチャしないラムダは通常の関数ポインターに崩壊する可能性があります)
AndyG

3
反対に、直接呼び出される代わりにどこかに渡される場合、関数の代わりにラムダを使用するとインライン展開が促進されます。当然、そのために関数ポインターをにパックすることができますstd::integral_constant...
Deduplicator

@Deduplicator: " 関数の代わりにラムダを使用すると、インライン化が促進されます "明確に言えば、これは、「どこかに」が、関数ポインターではなく、呼び出し可能な関数として呼び出す関数受け取るテンプレートである場合にのみ適用されます。
Nicol Bolas

2
@AndyG as-ifルールを忘れている:ラムダのサイズを要求しないプログラム(…なぜですか?!)も、それへのポインタも作成しないため、必要はありません(通常は必要ありません)。そのためのスペースを割り当てます。
Konrad Rudolph


8

「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?

一定レベルの複雑さの問題には、少なくとも同じ複雑さの解決策が必要です。しかし、同じ問題に対してそれほど複雑ではないソリューションがある場合、より複雑なソリューションを使用する正当な理由はありません。なぜ必要のない複雑さを導入するのですか?

ラムダと関数の間では、関数は単純に2つのエンティティのより単純な種類です。ラムダを使用しないことを正当化する必要はありません。1つを使用して正当化する必要があります。ラムダ式は、通常のすべての特別なメンバー関数、関数呼び出し演算子、およびこの場合は関数ポインターへの暗黙の変換演算子を持つ名前のないクラス型であるクロージャー型を導入し、その型のオブジェクトを作成します。ラムダ式からグローバル変数をコピーして初期化することは単に関数を定義するだけではありません。6つの暗黙的に宣言された関数でクラス型を定義し、さらに2つの演算子関数を定義して、オブジェクトを作成します。コンパイラーはもっと多くのことをしなければなりません。ラムダの機能が必要ない場合は、ラムダを使用しないでください…


6

ラムダは無名関数です。

名前付きラムダを使用している場合、それは基本的に名前付き無名関数を使用していることを意味します。この矛盾を回避するには、関数を使用することもできます。


1
これは用語からの議論のようです。わかりにくい名前と意味。名前付きラムダが匿名ではないことを定義することで、簡単に反駁できます(duh)。ここでの問題は、ラムダがどのように使用されるかではなく、「無名関数」が誤称であり、ラムダが名前にバインドされている場合はもはや正確ではないということです。
Konrad Rudolph

@KonradRudolph:用語だけでなく、そもそもそれらが作成された理由です。少なくとも歴史的には、ラムダは匿名関数であり、そのため、特に関数が期待されている場合は特に、それらに名前を付けるのは混乱します。
Eric Duminil

1
いや。ラムダは日常的に(通常はローカルで)名前が付けられますが、混乱することはありません。
Konrad Rudolph

1
匿名関数に同意しないと、それはより機能的なものですが、とにかく、それらを右辺値参照としてのみ使用することを強制するのではなく、(名前付きの)インスタンスがあるという問題は発生しません。(あなたのポイントはローカルスコープにも当てはまるはずです)
Jarod42

5

それをラムダのままにしない理由があるなら?「通常の」グローバル関数の代わりにラムダをどこでも使用しない理由はありますか?

以前はグローバルファンクタの代わりに関数を使用していたため、一貫性と最小の驚異の原則が破られました。

主な違いは次のとおりです。

  • 関数はオーバーロードできますが、ファンクタはオーバーロードできません。
  • 関数はファンクタではなくADLで見つけることができます。
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.