30

編集、私がこの質問をした11年後:私は質問したことで立証されたと感じています!C++20はついに十分に近い何かをしました。

元の質問は以下のとおりです。

-

私は多くのPythonプログラムでyieldを使用してきましたが、多くの場合、コードが実際にクリアされます。私はそれについてブログを書きました、そしてそれは私のサイトの人気のあるページの1つです。

C#はyieldも提供します。これは、呼び出し側の状態保持を介して実装され、状態、関数のローカル変数などを保持する自動生成されたクラスを介して実行されます。

私は現在、C++0xとその追加について読んでいます。また、C ++ 0xでのラムダの実装について読んでいると、ラムダコードを格納するoperator()を備えた、自動生成されたクラスを介して行われたことがわかりました。私の頭の中に形成された自然な質問:彼らはラムダのためにそれをしました、なぜ彼らは「収量」のサポートのためにもそれを考慮しなかったのですか?

確かに彼らはコルーチンの価値を見ることができます...だから私は彼らがマクロベースの実装(Simon Tathamのような)を適切な代替物として考えていると推測することができるだけです。ただし、多くの理由でそうではありません。呼び出し先が保持された状態、再入可能でない、マクロベース(それだけで十分な理由)などです。

編集: yieldガベージコレクション、スレッド、またはファイバーに依存しません。Simonの記事を読んで、次のような単純な変換を行うコンパイラについて話していることがわかります。

int fibonacci() {
    int a = 0, b = 1;
    while (true) {
        yield a;
        int c = a + b;
        a = b;
        b = c;
    }
}

の中へ:

struct GeneratedFibonacci {
    int state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            state = 1;
            while (true) {
                return a;

        case 1:
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

ガベージコレクション?いいえ、スレッドですか?いいえ。繊維?いいえ。単純な変換ですか?間違いなく、はい。

4

8 に答える 8

22

なぜこのようなものを追加しなかったのかはわかりませんが、ラムダの場合、言語に追加されただけではありません。

彼らは、Boost のライブラリ実装としての生活を始めました。

  • ラムダは広く有用です: 利用可能になったときに多くの人が使用します。
  • C++03 でのライブラリの実装には多くの欠点があります。

これに基づいて、委員会は C++0x である種のラムダを採用することを決定しました。最初は、Boost よりも優れたライブラリ実装を可能にするために、より一般的な言語機能を追加することを試みたと思います。

そして最終的に、他の選択肢がなかったので、それをコア言語機能にしました。十分に優れたライブラリ実装を作成することができなかったからです。

新しいコア言語機能は、単に良いアイデアだと思われるから言語に追加されるわけではありません。委員会はそれらを追加することに非常に消極的であり、問​​題の機能は本当にそれ自体を証明する必要があります. 特徴が次のとおりであることを示す必要があります。

  • コンパイラで実装可能、
  • 真のニーズを解決し、
  • ライブラリの実装だけでは十分ではありません。

キーワードの場合yield、最初のポイントを解決できることがわかっています。あなたが示したように、それは機械的に行うことができるかなり単純な変換です.

2点目はややこしい。これの必要性はどれくらいありますか?存在するライブラリの実装はどの程度広く使用されていますか? 何人の人がこれを要求したり、提案したりしましたか?

最後のポイントも通過するようです。少なくともC++03では、あなたが指摘したように、ライブラリの実装にはいくつかの欠陥があり、コア言語の実装を正当化する可能性があります。ただし、C++0x でより良いライブラリの実装を行うことはできますか?

したがって、主な問題は実際には興味の欠如にあると思います。C++ はすでに巨大な言語であり、追加される機能に本当に価値がある場合を除き、C++ が大きくなるのを望んでいません。これだけでは十分ではないと思います。

于 2010-12-13T17:43:18.710 に答える
8

キーワードを追加すると、以前は有効だったコードが無効になるため、常に注意が必要です。C++ と同じくらい大きなコード ベースを持つ言語では、それを回避しようとします。

C++ の進化は公開プロセスです。そこに参加する必要があると思われる場合はyield、C++ 標準委員会に適切な要求を作成してください。

決定を下した人々から直接、あなたの答えが得られます。

于 2010-10-05T14:16:44.870 に答える
7

そのため、C++11 または C++14 には対応していないように見えますが、C++17 には対応している可能性があります。講義C++ コルーチン、 CppCon2015 からの負のオーバーヘッドの抽象化、およびこちらの論文をご覧ください。

要約すると、関数の機能として yield と await を持つように c++ 関数を拡張する作業を行っています。彼らは Visual Studio 2015 で初期実装を行っているようですが、clang にまだ実装があるかどうかはわかりません。また、yield と await をキーワードとして使用すると、問題が発生する可能性があるようです。

このプレゼンテーションが興味深いのは、一連の処理を続行するためにデータが入ってくるのを待つネットワーク コードがどれほど単純化されたかについて彼が語っているからです。驚くべきことに、これらの新しいコルーチンを使用すると、現在よりも高速でコードが少なくなるようです。素晴らしいプレゼンテーションです。

C++ の再開可能な関数の提案は、ここにあります。

于 2016-02-05T13:46:26.947 に答える
7

彼らはラムダのためにそれをやったのに、なぜ利回りをサポートすることも考えなかったのですか?

論文を確認してください。誰かがそれを提案しましたか?

...マクロベースの実装が適切な代替手段であると彼らが考えていると推測することしかできません。

必ずしも。彼らはそのようなマクロソリューションが存在することを知っていると確信していますが、それらを置き換えるだけでは、新しい機能を通過させるのに十分な動機にはなりません.


新しいキーワードに関してはさまざまな問題がありますが、ラムダに対して行われたり、関数の戻り値の型として auto を使用したりするなど、新しい構文でそれらを克服できます。

急進的な新機能には、急進的な変更に懐疑的な人が常にたくさんいるため、機能を完全に分析して委員会にプッシュするための強力なドライバー (つまり、人) が必要です。したがって、yield コンストラクトに対する強力な技術的理由と見なされるものがなくても、十分なサポートが得られなかった可能性があります。

しかし基本的に、C++ 標準ライブラリは、yield で見られるものとは異なる反復子の概念を採用しています。2 つの操作しか必要としない Python の反復子と比較してください。

  1. an_iter.next() は次のアイテムを返すか、StopIteration を発生させます (メソッドを使用する代わりに 2.6 に含まれる next() ビルトイン)
  2. iter(an_iter) は an_iter を返します (したがって、関数内でイテラブルとイテレータを同じように扱うことができます)

C++ の反復子はペアで使用され (同じ型である必要があります)、カテゴリに分割されます。これは、yield コンストラクトにより適したものに移行するためのセマンティック シフトであり、そのシフトは概念にうまく適合しません (これは、それ以来ドロップされましたが、それは比較的遅くなりました)。たとえば、範囲ベースの for ループを、この異なる形式のイテレータをより簡単に記述できる形式に変更するという私のコメントを拒否した理由を (残念ながら正当な理由で)参照してください。

さまざまな反復子フォームについて私が何を意味するかを具体的に明確にするために、生成されたコード例には、反復子型である別の型と、それらの反復子を取得および維持するための関連する機構が必要です。処理できないわけではありませんが、最初に想像するほど単純ではありません。本当の複雑さは、「ローカル」変数の例外 (構築中を含む) に関する「単純な変換」、ジェネレーター内のローカル スコープでの「ローカル」変数の有効期間の制御 (ほとんどは呼び出し間で保存する必要がある) などです。

于 2010-10-06T13:03:26.460 に答える
2

一般に、委員会の論文で何が起こっているかを追跡できますが、特定の問題を調べるよりも追跡する方がよいでしょう。

C ++委員会について覚えておくべきことの1つは、それがボランティア委員会であり、やりたいことをすべて達成できるわけではないということです。たとえば、元の標準にはハッシュタイプのマップがありませんでした。これは、時間内に作成できなかったためです。yield委員会には、仕事を確実に遂行するために十分な配慮とそれが何をしているのかを気にかけている人がいなかった可能性があります。

調べるための最良の方法は、アクティブな委員会のメンバーに尋ねることです。

于 2010-10-05T14:34:53.957 に答える
2

まあ、そのような些細な例では、私が見る唯一の問題std::type_info::hash_code()は指定されていないということですconstexpr. 私は、適合する実装がそれを実現し、これをサポートできると信じています。とにかく、本当の問題は一意の識別子を取得することなので、別の解決策があるかもしれません。(明らかに、私はあなたの「マスタースイッチ」構造を借りました、ありがとう。)

#define YIELD(X) do { \
    constexpr size_t local_state = typeid([](){}).hash_code(); \
    return (X); state = local_state; case local_state: ; } \
while (0)

使用法:

struct GeneratedFibonacci {
    size_t state;
    int a, b;

    GeneratedFibonacci() : state (0), a (0), b (1) {}

    int operator()() {
        switch (state) {
        case 0:
            while (true) {
                YIELD( a );
                int c = a + b;
                a = b;
                b = c;
            }
        }
    }
}

うーん、ハッシュが 0 でないことも保証する必要があります。また、DONEマクロは簡単に実装できます。


本当の問題は、ローカル オブジェクトを持つスコープから戻ったときに何が起こるかです。C ベースの言語でスタック フレームを保存する見込みはありません。解決策は実際のコルーチンを使用することであり、C++0x はスレッドとフューチャーで直接対処します。

このジェネレーター/コルーチンを考えてみましょう:

void ReadWords() {
    ifstream f( "input.txt" );

    while ( f ) {
        string s;
        f >> s;
        yield s;
    }
}

同様のトリックが に使用された場合yieldf最初の で破棄され、yieldその後ループを継続することは違法です。POD 以外のオブジェクト定義を通過できないgotoか通過できないためです。switch

于 2010-10-09T19:47:08.220 に答える
1

ユーザー空間ライブラリとしてコルーチンの実装がいくつかあります。ただし、これが取り引きです。これらの実装は、非標準の詳細に依存しています。たとえば、c++ 標準のどこにも、スタック フレームの保持方法が指定されていません。ほとんどの実装はスタックをコピーするだけです。これは、ほとんどの C++ 実装が動作する方法だからです

標準に関しては、c++ はスタック フレームの仕様を改善することで、コルーチンのサポートを支援できたはずです。

実際、それを言語に「追加」することは、私には良い考えではありません。完全にコンパイラに依存するほとんどの場合、「十分に良い」実装に固執するからです。コルーチンの使用が重要な場合、これはとにかく受け入れられません

于 2010-12-13T16:31:40.000 に答える
0

まず@Potatoswatterに同意してください。

コルーチンをサポートすることは、ラムダをサポートすることと同じではなく、ダフのデバイスで遊んだような単純な変換でもありません。

Python でジェネレーターのように機能するには、完全な非対称コルーチン(スタックフル)が必要です。Simon TathamChris の実装はどちらもスタックレスですが、 Boost.Coroutineは重いですがスタックフルです。

残念ながら、C++11 にはまだyieldコルーチンがありません。おそらく C++1y です ;)

PS: Python スタイルのジェネレーターが本当に好きなら、これを見てください。

于 2013-03-26T07:56:08.347 に答える