web-dev-qa-db-ja.com

C ++ 11のラムダ式がデフォルトの引数をサポートしている場合はどうなりますか?

次のコードは非常に便利で害はないと思います。

auto fn = [](bool b = false) -> int // NOT legal in C++11
{
    return b ? 1 : 0;
}; 

C++ 11がラムダ式のデフォルト引数を明示的に禁止しているのはなぜですか?

背後にある理論的根拠と考慮事項について疑問に思います。

C++ 11標準が言う「何」ではなく「なぜ」を知りたいのです。

29
xmllmx

ラムダがデフォルトの引数を持つことができないという本当の理由はありません。ただし、ラムダを使用する主な方法は2つあり、型システムを変更せずにデフォルトの引数を許可するのはそのうちの1つだけです。

  1. ラムダを直接呼び出すことも、テンプレートを介して呼び出すこともできます。この場合、デフォルトのパラメータで問題なく動作します。

  2. _std::function_を介してラムダを呼び出すことができます。デフォルトのパラメータは、型システムを変更しないと機能しません。

私の推測では、C++ 11で作成する新しい関数は、通常_std::function_パラメーターを取ります。この方法では、関数は取りません。 haveをテンプレートにするか、渡されるラムダとファンクターごとにインスタンス化する必要はありません。

_std::function_(または関数ポインタ)がデフォルトをとれないのはなぜですか?

そのような関数のタイプが何であるかは明らかではありません。

  • std::function<int(bool)>の場合、デフォルトでどのように呼び出しますか? (できません。)

  • std::function<int(bool=false)>の場合、どのタイプと互換性があり、変換はどのように機能しますか? std::function<int()>に変換できますか? std::function<int(bool=true)>はどうですか?

  • std::function<int(bool=default)>thenのような新しいものの場合、どのタイプと互換性があり、変換はどのように機能しますか?

基本的に、これは、標準を切り替えて関数ポインタ/ _std::function_にデフォルトの引数を処理させることができる単なるスイッチではありません。通常の関数のデフォルト引数は、関数の宣言からの情報を使用して処理されます。この情報は、ラムダまたは関数ポインターの呼び出しサイトでは使用できません。したがって、デフォルトに関する情報を関数型にエンコードしてから、変換と互換性に関するすべての非自明なルールを理解する必要があります。

したがって、なぜそのような機能が追加されるのかについて説得力のある事例を考え出し、委員会を説得する必要があります。

では、なぜラムダはデフォルトを取ることができないのですか?

私はこの質問に答えていません。しかし、追加するのはあまり便利な機能ではないと思います。できればこの回答を削除しますが、受け入れられました。できれば反対票を投じますが、それは私のものです。 C'est lavie。

19
Dietrich Epp

質問へのコメントが示唆しているように、デフォルトの引数がない理由はおそらくありませんtechnical理由はありません。しかし、逆の質問は「デフォルトの引数の実用的理由はありますか?」です。これに対する答えは「いいえ」だと思いますが、その理由はここにあります。

ラムダを呼び出すためにできることの1つは、すぐに呼び出すことです

_[] { printf("foo"); }();
_

用途が限られていると思いますので、次に進みましょう。ラムダを呼び出す他の唯一の方法は、最初にラムダを変数にバインドすることです。ここにはいくつかのオプションがあります。

  1. 自動を使用します。したがって、auto foo = [] { printf("foo"); }; foo();を取得します
  2. 関数ポインタにバインドします:void (*foo)() = [] { printf("foo"); }; foo()。もちろん、これはラムダがキャプチャしない場合にのみ機能します。
  3. ラムダを関数または関数テンプレートに渡すことにより、1または2と同等の処理を行います。

それでは、それぞれの場合のデフォルト引数の有用性を見ていきましょう。

  1. この場合、ラムダを直接呼び出しているので、コードはおそらく十分に緊密であるため、さまざまな数の引数を使用してラムダを呼び出すことはありません。もしそうなら、ラムダはおそらくより一般的なコンポーネントにリファクタリングされる可能性があります(すべきですか?)。ここでは実際的なメリットは見られません。
  2. (1を参照)
  3. ラムダを別の関数に渡します。ここでも、デフォルトの議論に実際的なメリットは見られません。古き良きファンクター(デフォルトの引数を持つことができます)を思い出してください-これらにもデフォルトの引数が存在することを期待する関数が多すぎることを私は知っているとは言えません。ラムダは事実上単なるファンクターであるため、この観測値が突然変化する理由はありません。

これらの点は、ラムダへのデフォルトの引数が実際にはそれほど有用ではないと言うのに十分だと思います。また、デフォルトの引数がある場合、ラムダの型に関する問題について話している人もいますが、これは問題のないIMOです。同じことを実行し、doesがデフォルトの引数を持つ独自のファンクターをいつでも作成できます。また、関数ポインタへの劣化についても、言うことはあまりありません。通常の機能を取る

_void func(int i = 0)
{
}
_

そしてそれのアドレスを取ります。あなたは何を得ますか? void (*)(int)。ラムダが異なるルールに従う理由はありません。

ラムダのデフォルト引数が機能することを許可するための実際の「技術的」制限がないことに同意しますそれ自体場合によっては。関数の型はデフォルトの引数の影響を受けないため、ポインターとautoが壊れることはありません。しかし、それがこれがひどく実用的ではない理由でもあります。

どうして?

デフォルトの引数は、関数シグネチャの一部ですが、関数型の一部ではないため、次のようになります。

[C++11: 1.3.17]:
署名
<function>名、パラメータータイプリスト(8.3.5)、および囲んでいる名前空間(存在する場合)
[注:署名は、名前のマングリングとリンクの基礎として使用されます。 —エンドノート]

[C++11: 8.3.5/6]:[..]戻り値の型、parameter-type-listref-qualifier、およびcv -qualifier-seqですが、デフォルトの引数(8.3.6)または例外仕様(15.4)は、関数型の一部ではありません。 [注:関数タイプは、関数へのポインター、関数への参照、およびメンバー関数へのポインターの割り当てと初期化中にチェックされます。 —エンドノート]

これらは本質的に、コンパイラーが使用する関数の宣言を確認できることによって「アクティブ化」され、関数呼び出しの時点で挿入される構文糖衣です。

#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

int main()
{
   foo();
}
  • 出力:5

デフォルトの引数は「表示」です。

ただし、関数をポインターの後ろに「隠す」と、次のようになります。

int main()
{
    void (*bar)(int) = &foo;
    bar();
}
  • エラー:too few arguments to function

barのタイプは正しく、コンパイラーはfooにデフォルトがあることを認識していますが、コンパイラーに通知するためのdirect構文は存在しません。 barを呼び出すとbarfooになります。確かに、この些細なシナリオでは、割り当てを観察することでそれを理解することができますが、それはより広い議論を正当化することはほとんどありません。

同じ理由で、呼び出しサイトに表示されない定義でのみ記述されているデフォルトの引数はほとんど役に立ちません。

// a.h
void foo(int);

// a.cpp
#include "a.h"
#include <iostream>

void foo(int x = 5)
{
   std::cout << x << '\n';
}

// main.cpp
#include "a.h"

int main()
{
    foo();
}
  • Main.cppのエラー:too few arguments to function

これが理由だと思います:

[C++11: 8.3.6/4]:[..]異なるスコープの宣言には、完全に異なるデフォルト引数のセットがあります。 [..]

areクラスの非テンプレートメンバー関数定義のデフォルト引数を「積み上げる」ことができます([C++11 8.3.6/6]);この例は、このデフォルトが同じTUにのみ適用されることを示しています。これは、上記の2番目のコードスニペットで見た動作に従います。

したがって、デフォルトの引数が関数型の一部ではなく、呼び出しサイトに明確に表示される必要がある場合、ラムダに役立つ、些細な工夫が施されたコーナーケースはほんの一握りです。それらが作成されたのと同じスコープで、コンパイラーがラムダへの呼び出しのデフォルト引数を「入力」する方法を簡単に理解できるようにします。それで、そのポイントは何ですか?それほど多くはありません、私はあなたに言います。