2014-04-30-01-代入と move:気付いたことをいくつか
>> Site top >> weblog >> 月別アーカイブ >> 2014年04月のlog >> 2014-04-30-01-代入と move:気付いたことをいくつか
最終更新日付:2014/04/30 01:00:00
代入と move:気付いたことをいくつか
2014 年 04 月 30 日
ごちゃごちゃとコードをイジっているうちに、気がついたら代入構文を実装してしまった。反省しつつ、気付いたことを。
マクロ opr= が第一パラメータを代入先の場所とみなし、総称関数 operator= をコールして‥‥‥というのは問題ないようなのだが、良く考えてみると以下のような書き方ができないことがわかる。
(stl:opr= (stl:opr= foo 10) 20)
加算と代入、および減算と代入の合わせ技である += / -= なども同様だ。C++ では通常、代入演算子は代入先の『非 const 参照』を返す。だから、代入式が返す値に対してさらに代入をかけることができる。つまり、(foo = 10) = 20; ということだ。しかし、例の opr= マクロは結果として左辺の『値』を返すことしかできない。そりゃそうだ。Common Lisp には(C++ の文脈でいうところの)「参照」などというモノは存在しないのだから。
では、自分でこういう書き方をするだろうか? 絶対ノーだ。しかし、可能な限り「同じことができる」ようにはしておきたい‥‥‥その一方で、現時点でも「やりすぎだ」という声が(自分の中から)聞こえてきている。自分がそもそもやりたかったのは STL のパワーを Common Lisp でも使えるようにすることだった。決して、Common Lisp の上に C++ を作り込むことではない。だったら無理をすることはないじゃないか‥‥‥うん、そう考えることにしよう。
さて、気になっていることはまだある。move 代入のトリガーとなる operator= の特定化、あれに穴があくことはないだろうか、ということだ。どういうことか、以下に。
まず、代入構文の opr= マクロは、なんやかやで以下の総称関数 operator= 呼び出しに展開される。デフォルトでは「右辺の値」をそのまま返す。ここではデバッグ用の情報だけを出力しておく。
(defgeneric operator= (lhs rhs)) (defmethod operator= (lhs rhs) ; [A] (format t "(operator= T T) called.~%") rhs)
さて、remove-reference の登場だ。(stl:opr= foo (stl:move bar)) みたいな記述でムーブの動作をさせるため、operator= の特殊化を以下のようにしておく。実際には、ここから先日 の総称関数 operator-move がコールされることになるわけだ。
(defclass remove-reference () ()) (defmethod operator= (lhs (rhs remove-reference)) ; [B] (format t "(operator= T remove-reference) called.~%") rhs)
自分が心配しているのは、CL-STL 自身が関知しないところで operator= のメソッドがさらに追加されることにより、代入元に remove-reference が指定されても上記のメソッドがコールされない場合があるのではないか、ということだ。総称関数呼び出しが実行時に解決される仕組みを考えると、それはどうやらありそうに思える。とりあえず、なんらかのクラス同士の『代入』を考えるとしよう。以下のように。
(defclass foo () ()) (defmethod operator= ((lhs foo) (rhs foo)) ; [C] (format t "(operator= foo foo) called.~%") rhs) (defclass bar () ()) (defmethod operator= ((lhs bar) (rhs bar)) ; [D] (format t "(operator= bar bar) called.~%") rhs)
この状態でも、foo や bar の代入において代入元を remove-reference で括った場合でも意図した動作をする。つまり、ちゃんと [B] がコールされるのだ。以下のように。
* (let ((foo (make-instance 'foo)) (bar (make-instance 'bar)) (rm (make-instance 'remove-reference))) (operator= foo foo) (operator= bar bar) (operator= foo bar) (operator= foo rm) (operator= bar rm) (values)) => (operator= foo foo) called. (operator= bar bar) called. (operator= T T) called. (operator= T remove-reference) called. (operator= T remove-reference) called.
ここまではいい。しかし、(まぁ考え難いが)以下のようにして foo がどんな代入元でも受け付けるようにした途端、[B] はコールされなくなってしまう。
(defmethod operator= ((lhs foo) rhs) ; [E] (format t "(operator= foo T) called.~%") rhs) * (let ((foo (make-instance 'foo)) (bar (make-instance 'bar)) (rm (make-instance 'remove-reference))) (operator= foo foo) (operator= bar bar) (operator= foo bar) (operator= foo rm) (operator= bar rm) (values)) => (operator= foo foo) called. (operator= bar bar) called. (operator= foo T) called. (operator= foo T) called. (operator= T remove-reference) called.
これはまぁ、当たり前の動作だ。パラメータの左から右に向かって順番に、もっとも特定的なメソッドが選択されるのだから。最初のパラメータで foo がそのものズバリでマッチし、2番目のパラメータがいかなる条件も要求していなければたしかにそうなる。そうならないようにしたければ、第2パラメータが remove-reference の場合で個別に特定化するしかないだろう。それはそうなのだが、それをユーザコードに強いるのが嫌なのだ。
ひとまずのところ、これをエレガントに解決する方法は思いつかない。思いついたことと言えば、opr= マクロの時点で operator-move と operator= を振り分けることくらいだった。しかし、それも何か間違っているような気がするのだ。自分がまだ良く知らない総称関数の仕組みの中に、何か役に立つモノはあるだろうか。調べてみよう。
コメント
このページのタグ
Page tag : Common Lisp
Page tag : STLとその移植
Copyright(C) 2005-2021 project-enigma.
Generated by CL-PREFAB.