7

Dで範囲を使おうとすると、惨めに失敗します。

Dで範囲を使用する適切な方法は何ですか?(私の混乱については、インラインコメントを参照してください。)

void print(R)(/* ref? auto ref? neither? */ R r)
{
    foreach (x; r)
    {
        writeln(x);
    }

    // Million $$$ question:
    //
    // Will I get back the same things as last time?
    // Do I have to check for this every time?

    foreach (x; r)
    {
        writeln(x);
    }
}

void test2(alias F, R)(/* ref/auto ref? */ R items)
{
    // Will it consume items?
    // _Should_ it consume items?
    // Will the caller be affected? How do I know?
    // Am I supposed to?
    F(items);
}
4

4 に答える 4

8

まだ読んでいない場合は、範囲に関するこのチュートリアルを読む必要があります。

範囲が消費される場合と消費されない場合は、そのタイプによって異なります。それが入力範囲であり、順方向範囲ではない場合(たとえば、ある種の入力ストリームであるstd.stdio.byLine場合は、この一例です)、何らかの形または形式で反復すると、それが消費されます。

//Will consume
auto result = find(inRange, needle);

//Will consume
foreach(e; inRange) {}

順方向範囲であり、参照型の場合、反復処理するたびに消費されますが、呼び出しsaveてコピーを取得できます。コピーを消費しても、元の消費は消費されません(また、元の消費も消費されません)。コピー)。

//Will consume
auto result = find(refRange, needle);

//Will consume
foreach(e; refRange) {}

//Won't consume
auto result = find(refRange.save, needle);

//Won't consume
foreach(e; refRange.save) {}

物事がより興味深いものになるのは、値型(または配列)である前方範囲です。それらは、に関しては他の順方向範囲と同じようsaveに機能しますが、単に関数に渡すか、foreach暗黙的にそれらを使用するという点で異なりsaveます。

//Won't consume
auto result = find(valRange, needle);

//Won't consume
foreach(e; valRange) {}

//Won't consume
auto result = find(valRange.save, needle);

//Won't consume
foreach(e; valRange.save) {}

したがって、順方向範囲ではない入力範囲を処理している場合は、関係なく消費されます。また、フォワードレンジを扱っている場合、save消費されないことを保証したい場合は電話する必要があります。そうでない場合は、消費されるかどうかはタイプによって異なります。

に関してはref、引数を取る範囲ベースの関数を宣言するとref、それはコピーされないので、渡された範囲が参照型であるかどうかは関係ありませんが、それはあなたが右辺値を渡すことはできません。これは非常に煩わしいため、ref実際に元の値を常に変更する必要がない限り、範囲パラメータを使用しないでください(たとえば、元の値を明示的に変更するのではなく、元の値を明示的に変更std.range.popFrontNするため、refコピー)。

順方向範囲を使用した範囲ベースの関数の呼び出しに関しては、値型範囲が適切に機能する可能性が最も高くなります。これは、コードが値型範囲で記述およびテストされ、参照型で常に適切にテストされるとは限らないためです。残念ながら、これにはPhobosの関数が含まれます(ただし、修正される予定です。まだすべてのケースで適切にテストされていません。Phobos関数が参照型の前方範囲で適切に機能しない場合は、報告してください)。したがって、参照型の前方範囲は、常に正常に機能するとは限りません。

于 2012-06-25T18:08:57.717 に答える
4

申し訳ありませんが、これをコメントに収めることはできません:D. Range が次のように定義されているかどうかを検討してください。

interface Range {
    void doForeach(void delegate() myDel);
}

そして、あなたの関数は次のようになりました:

void myFunc(Range r) {
    doForeach(() {
        //blah
    });
}

r を再割り当てしたときに何か奇妙なことが起こるとは思わないでしょうし、呼び出し元の Range を変更できるとは思わないでしょう。問題は、特殊化を利用しながら、テンプレート関数が範囲型のすべてのバリエーションを考慮できることを期待していることだと思います。それはうまくいきません。テンプレートにコントラクトを適用して、特殊化を利用したり、一般的な機能のみを使用したりできます。これはまったく役に立ちますか?

編集(コメントで話していたこと):

void funcThatDoesntRuinYourRanges(R)(R r)
if (isForwardRange(r)) {
    //do some stuff
}

編集 2 std.rangeが定義されているisForwardRangeかどうかを単純にチェックするように見え、範囲のリンクされていないコピーのようなものを作成する単なるプリミティブです。ドキュメントは、ファイルやソケットなどに対して定義されていないことを指定しています。savesavesave

于 2012-06-25T15:31:42.793 に答える
1

それの短い; 範囲が消費されます。これは、期待して計画する必要があるものです。

foreach の ref はこれには何の役割も果たさず、範囲によって返される値にのみ関連します。

長い; 範囲は消費されますが、コピーされる可能性があります。何が起こるかを判断するには、ドキュメントを参照する必要があります。値の型がコピーされるため、関数に渡されたときに範囲が変更されない場合がありますが、データ ストリームが FILE などの参照になる可能性があるため、範囲が構造体として渡される場合は信頼できません。そしてもちろん、ref 関数のパラメーターは混乱を招きます。

于 2012-06-25T15:00:57.620 に答える
1

関数printが次のようになっているとします。

void print(R)(R r) {
  foreach (x; r) {
    writeln(x);
  }
}

ここでrは、ジェネリック型 : を使用して参照セマンティクスを使用して関数に渡されるため、ここRは必要ありませんref(autoコンパイル エラーが発生します)。それ以外の場合、これは の内容をrアイテムごとに出力します。(範囲には特定のプロパティがあるため、ジェネリック型を範囲の型に制約する方法があったことを覚えているようですが、詳細を忘れてしまいました!)

ともかく:

auto myRange = [1, 2, 3];
print(myRange);
print(myRange);

...出力します:

1
2
3
1
2
3

関数を次のように変更した場合(x++範囲に意味があると仮定します):

void print(R)(R r) {
  foreach (x; r) {
    x++;
    writeln(x);
  }
}

...その後、各要素は出力される前に増加しますが、これはコピー セマンティクスを使用しています。つまり、元の値はmyRange変更されないため、出力は次のようになります。

2
3
4
2
3
4

ただし、関数を次のように変更した場合:

void print(R)(R r) {
  foreach (ref x; r) {
    x++;
    writeln(x);
  }
}

...その後、xは の元の要素を参照する参照セマンティクスに戻されますmyRange。したがって、出力は次のようになります。

2
3
4
3
4
5
于 2012-06-25T15:02:09.280 に答える