33

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

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

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

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

C++11規格の「何」ではなく「なぜ」を知りたい。

4

4 に答える 4

23

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

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

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

私の推測では、C ++ 11で作成する新しい関数は通常、パラメーターを取りstd::functionます。これは、関数がテンプレートである必要がない、取得するすべてのラムダとファンクターに対してインスタンス化される必要がないためです。それに渡されます。

std::function(または関数ポインタ)がデフォルトをとることができないのはなぜですか?

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

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

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

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

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

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

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

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

于 2013-01-07T04:19:09.407 に答える
5

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

なんで?

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

[C++11: 1.3.17]:
署名
<関数>名、パラメーター型リスト(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

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

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

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

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

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

// 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]: [..]異なるスコープでの宣言には、完全に異なるデフォルト引数のセットがあります。[..]

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

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

于 2013-01-07T12:56:56.637 に答える
3

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

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

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

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

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

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

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

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

void func(int i = 0)
{
}

そしてそれのアドレスを取ります。何がもらえますか?A。void (*)(int)_ ラムダが異なる規則に従う理由はありません。

于 2013-01-07T04:12:46.207 に答える
0

私はこれらの応答のいくつかを読みました、そして誰もが(私がtl; drを読んだものから)型システムの制限に言及していました。これは実際にはそれとは何の関係もありません。関数の型にはデフォルトかどうかに関係なくすべてのパラメーターが含まれますが、デフォルトの引数を持つファンクターはファジー関数と考えることができます。その型は、それを見るまで生まれません。

ラムダは、ファンクターの使用を単純化するためのシンタックスシュガーであり、いくつかのクールな型の推論がスローされます。これは、コンパイラーによって煮詰められて変換されると、キャプチャー(ない場合は関数)がある場合のファンクターです。したがって、ラムダでそれを使用できない理由は実際にはありません。観察:

#include<functional>
#include<iostream>

int main()
{
    // my lambda as written as a functor.  Not pretty eh?  But it works. o.O
    struct {
    void operator()(char const* x = "default") {
         std::cout << x << std::endl;
    } } fn;

    fn("Hi");
    fn();
    std::function<void(char const*)> fn_copy_1 = fn;
    std::function<void()>            fn_copy_0 = fn;
    fn_copy_1("there");
    fn_copy_0();
    return 0;
}

結果(ここで例を実行)

Hi
default
there
default

型システムに変更を加えることなくすべて。

では、なぜそのような制限があるのでしょうか。私の推測では、誰もそれが有用であるとは思っていませんでしたし、それが実装されていない場合はテストする必要が少なくなります。ユーザーが機能Xが必要であると呼びかけることで、言語に物事が追加されます。誰もそれを要求せず、それに関連付けられた十分な価値がない場合、それは追加されません。これは、将来追加できないということではなく、必要な場合に限ります。

于 2021-10-30T19:49:37.637 に答える