On Error Resume Next (解決編)

大抵は自分が間違えている

前回の記事でエラーを解決せずに全部投げてオチをつけてしまいましたが、片がついたのでメモを残します。

clear を呼べない問題

Point[] 型の変数 buf に対して buf.clear を呼ぶと cannot deduce で以下の通り怒られた問題について:

Error: template `object.clear` cannot deduce function from argument types `!()(Point[])`
candidates are:
  `object.clear(T : Value[Key], Value, Key)(T aa)`
  `object.clear(T : Value[Key], Value, Key)(T* aa)`

ただのエラーメッセージ読め案件だったので恥ずかしいんですが、上記を読むとどうもobjectモジュールのclearテンプレートを展開しようとして失敗していますね。 僕が呼びたいのは Point[] 型の clear メソッドなんですけど……? UFCS って obj.method(a, b, ...) に合うシグネチャのメソッドが見つからなかったときに初めて method(obj, a, b, ...) を探しに行くんじゃなかったんです?

しかし愚かなのは僕の方でした。 そうです、D言語の動的配列に clear メソッドは存在しないのです。 clear が存在するのは Appender オブジェクトか std.container モジュールに収められているコンテナオブジェクトだけです。 当然動的配列にもあるもんだと思っていた……。

というわけで、この問題の解決法は buf.clear を以下の通り修正することです:

// 再代入する
buf = [];

// length プロパティは set 可能なのでこれでもよい
buf.length = 0;

cannot deduce とか言われると推論のための情報が足りないのかと思ってしまうけど、そもそも呼ぶ対象を間違えているケースがあるのだと知りました。なるほどなあ。

SList!Point を使うと RE する問題

bufの型を SList!Point にすると buf.clear でもコンパイルが通ります。 通りますし、手元(DMD v2.083.0)でも動きますが、ジャッジサーバ(DMD v2.070.1)では全ケース RE になります。

こればっかりはソースをいくら睨んでも解決しないと思ったので、ジャッジサーバの環境を手元に入れました。 仮想環境組むの面倒だったんで、手元の環境一度アンインストールして古い実行環境を再インストールするとかいうよわよわムーブを見せつけてしまった。

余談ですが、アンインストーラを走らせたらD言語環境関連のあれこれがしっかりディスクから取り除かれるのに、なぜかD/dmd2/html/d/images/compiler-dmd.pngだけが残ったんですよ。 それがコイツなんですけど:

f:id:penpenpng:20190611191433p:plain

D言語くんは死んでなんかいない……今も君のディスクの中にいるよ……*1

閑話休題。とあるコードが手元(DMD v2.083.0)では走るのにジャッジサーバ(DMD v2.070.1)では実行時に落ちるという話でした。 その問題のコードの抜粋がこちら:

SList!Point buf;

// 中略

buf.insert(Point(x, y));

こちらをジャッジサーバ環境で走らせると、なるほど確かに実行時エラーを吐きますね:

core.exception.AssertError@std\container\slist.d(57): Assertion failure

すると気になるのは std\container\slist.d(57) です。ちょっと覗いてみます:

struct SList(T)
{

    // ……中略……

    private Node * _root;

    private void initialize() @trusted nothrow pure
    {
        if (_root) return;
        _root = cast (Node*) new NodeWithoutPayload();
    }

    private ref inout(Node*) _first() @property @safe nothrow pure inout
    {
        assert(_root);        // 57行目
        return _root._next;
    }

    // ……中略……

}

_rootnull なので怒られたらしいですね。 これ、もしや SList が初期化されてないだけでは?

というわけで解決策は buf 変数を宣言時に初期化しておくことです:

auto buf = make!(SList!Point);

めでたしめでたし*2

おわりに

ちょっとだけD言語くんと仲良くなれる兆しが見えたかもしれない。 それはそれとして、どうせ競プロのためにしか使わないなら手元のコンパイラのバージョンをジャッジサーバのものと揃えておくのが無難っぽいですね。

*1:正直怖かった。

*2:RE は解消されたがそもそも計算量がアレで TLE になったというオチがつく。