読者です 読者をやめる 読者になる 読者になる

変数への代入

VBA

VBAで、変数に値を代入してそれを直接別の関数に渡したい。
しかし通常の = による代入を使おうとしたら下記の「方法1」ではダメで、「方法2」のように2行に分ける必要がある。

' 「方法1」
? UBound(x = Array(1,2,3))    ' NG、xへの代入ではなく等値比較になる(エラー)
' 「方法2」
x = Array(1,2,3)              ' OK、xへの代入
? UBound(x)                   ' OK
 2

VBAでは式中に出てくる = は代入構文ではなく等値比較(==)とみなされる。代入の意味で = を使おうとしたら単独の代入文にしなければならない。言語の仕様なので、これはどうしようもない。

そこで代入関数 assign を作ってみる。

' ターゲットにソースの値を代入してターゲットを返す
Function assign(ByRef target As Variant, ByRef source As Variant) As Variant
    target = source
    assign = target
End Function

? UBound(assign(x, Array(1,2,3))     ' OK、だがこれでいいのか・・・?
 2
? UBound(x)                          ' xにもちゃんと代入されているようだ・・・
 2

この方法がダメなところは、assign 関数が返す値がターゲットとなる変数のコピーにすぎないことだ。このことは以下のコードでわかる。

' assign を2度繰り返してみる
? UBound(assign(assign(x, Array(1,2,3)), Array(1,2,3,4)))
 3                 ' 最終的に Array(1,2,3,4) が評価されている
? UBound(x)
 2                 ' しかし x は Array(1,2,3) で止まってる

これではコードゴルフが捗らないし、対象が大きな配列の場合コピーを返すコストは無視できない。
VT_BYREF を使ってなんとかならないか試しているが今のところうまくいっていない。たとえば、

//VARIANT変数をmove代入し、Target自身を返す
VARIANT __stdcall
moveAssign(VARIANT* target, VARIANT* source)
{
    VARIANT ret;
    ::VariantInit(&ret);
    if ( target != source )
    {
        std::swap(*target, *source);
        ::VariantClear(source);
    }
    ret.vt = VT_BYREF | VT_VARIANT;
    ret.pvarVal = target;
    return ret;
}

これではうまくいかなかった。

x = 0
? UBound(moveAssign(moveAssign(x, Array(1,2,3)),Array(1,2,3,4)))   ' 実行時エラー'13': 型が一致しません。

? UBound(x)
 2                 ' 最初の moveAssign 呼び出しは実行されているようだ