4

私は、クラスベースの有限ランダム アクセス範囲に取り組んできました。それにいくつかのテストを実行するとき:

auto myRange = /* construct my range */
static assert (isRandomAccessRange!(typeof(myRange))); // 
static assert (!isInfinite!(typeof(myRange)));         // both pass 
auto preamble = myRange[0..128];
assert( all!"a == 0"(preamble)); // check for all zeros

上記のスニペットの最後の行に関して、GDC 4.9.2 でこのコンパイル エラーが発生しました: "algorithm.d|4838|error: foreach: cannot make e ref"

エラーは、std.algorithm.find(find_if バリアント、範囲と述語を取る) のこのコード部分を指しています。これは、実際には次の各要素への参照を取りますforeach

InputRange find(alias pred, InputRange)(InputRange haystack)
if (isInputRange!InputRange)
{
    alias R = InputRange;
    alias predFun = unaryFun!pred;
    static if (isNarrowString!R)
    {
        ...
    }
    else static if (!isInfinite!R && hasSlicing!R && is(typeof(haystack[cast(size_t)0 .. $])))
    {
        size_t i = 0;
        foreach (ref e; haystack) // <-- needs a ref
        {
            if (predFun(e))
                return haystack[i .. $];
            ++i;
        }
        return haystack[$ .. $];
    }
    else
    {
       ...
    }
}

これは、引数opApplyを提供しないの実装を提供したために発生する可能性が最も高いです(クラスは他のメンバー関数への戻り値の型も提供しません)。refref

int opApply(int delegate(E) f) {...}
int opApply(int delegate(size_t,E) f) {...}

それを変更することもできますが、本当に気になるのは、現在、範囲クラスが関数の前提条件に準拠しており、foreachとにかく反復が引き続き機能することです。ドキュメントからの引用:

構造体およびクラス オブジェクトの反復は、範囲を使用して実行できます。の場合foreach、これは次のプロパティとメソッドを定義する必要があることを意味します。

プロパティ:

  • .empty それ以上要素がない場合は true を返します
  • .front 範囲の左端の要素を返す

方法:

  • .popFront() 範囲の左端を 1 つ右に移動する

これらはすべて提供されているので (それ以外の場合はランダム アクセス範囲にはなりません)、それらを使用する必要があります。代わりに、次に説明する別の反復方法を探している可能性があります。

集計式が構造体またはクラス オブジェクトであり、範囲プロパティが存在しない場合、foreach は特別なopApplyメンバー関数によって定義され、foreach_reverse 動作は特別なopApplyReverseメンバー関数によって定義されます。これらの関数の型は次のとおりです。

int opApply(int delegate(ref Type [, ...]) dg);

私の解釈では、これは探すべきではありませんでした。

またstd.algorithm.all、参照の反復を必要としないように見える も引用します。

bool all(Range)(Range range) if (isInputRange!Range && is(typeof(unaryFun!pred(range.front))));

入力範囲 range で見つかったすべての値 v が述語 pred を満たす場合に限り、true を返します。(最大で) Ο(range.length) 回の pred の評価を実行します。

これは Phobos ライブラリのバグで、std.algorithm.findそもそも値で反復する必要があるのでしょうか? それとも、私が見逃したことがありますか?

4

1 に答える 1

1

opApply範囲であると想定されるオブジェクトで宣言することさえ意味がありforeachませんopApply。確かに、、、およびopApplyの代わりに範囲型で が呼び出されている場合、それはコンパイラのバグです。その音から、コンパイラは を使用しているのに対し、使用していないため、誤って選択しています。ただし、宣言されていない限り、それを使用しなくても問題なく動作します。したがって、コンパイラがそれを持っている場合と持っていない場合に誤って使用するという事実ほど問題ではありません。frontpopFrontemptyopApplyopApplyreffrontfrontrefforeachrefopApplyrefopApplyopApplyreffront

したがって、コンパイラを修正する必要がありますが、これはおそらくこれまでに発見opApplyされたことがないでしょう。opApplyしたがって、範囲型で宣言しないようにコードを変更する必要があると私は主張します。そうすれば、この特定のバグに遭遇することさえありません。

そうは言っても、Phobos で問題となっているコードは、(クラスのような) 参照型である範囲に対してバグがあります。これは、反復時に呼び出しsaveに失敗するためです。haystackその結果、検索対象のスポットを参照するように元の範囲が変更されますが、要素が干し草の山の前からあったため、返されるポイントは正しいスポットをはるかに超えています。したがって、宣言を停止しopApplyたり、コンパイラのバグが修正されたりしても、範囲の参照型を使用している場合は、コードが機能し始めるために std.algorithm.find を修正する必要があります。

編集:

わかった。それは正しくありません。一部のコンパイラ開発者と話し合っているうちに修正されました。以前は、範囲関数が よりも優先されていましたopApply。それが仕様に記載されていることですが、ある時点で、より効率的である場合に範囲型がwithopApplyを使用して反復できるように、範囲関数よりも優先されるように変更されました。それは(ただし、明らかに範囲関数のリスクをもたらし、同じ動作をしないため、本当に厄介なバグが発生する可能性があります). したがって、仕様はコンパイラの現在の動作と一致せず、範囲型で宣言することはうまくいくはずです(ただし、明確なパフォーマンスの向上が得られない限り、そうしないことをお勧めします)。opApplyforeachopApplyopApply

そうは言っても、ここでエラーが発生するという事実は、依然としてコンパイラのバグです。をopApply使用しないためrefrefループ変数では機能しませんが、範囲関数は機能するため、その場合、コンパイラは範囲関数を呼び出す必要がありますが、明らかにそうではありません。いずれにせよ、レンジで使用する人はほとんどいないため、これは以前は検出されませんでした。これを使用opApplyする唯一の理由は、そうすることでパフォーマンスが向上するかどうかであり、仕様にまだ記載されているという事実は、範囲関数が優先されるopApplyため、そうでない場合よりも試した人がさらに少なくなります。

于 2015-02-05T12:05:31.943 に答える