kubo39's blog

ただの雑記です。

タプル型をキャストしたときに型変換が走らない話

連日のキャストとタプル型ネタです。

現行のD言語の驚くべき振る舞いの一つとして、タプル型をキャストにより変換した際に指定した型への変換が行われないということがある。

動作確認を行ったコンパイラのバージョンは以下。

)$ dmd --version
DMD64 D Compiler v2.098.1
Copyright (C) 1999-2021 by The D Language Foundation, All Rights Reserved written by Walter Bright

Cast Expressionの定義では

A CastExpression converts the UnaryExpression to Type.

と書いてあるが、以下のコードはなんと正常にコンパイルが通ってしまう。

alias TypeTuple(T...) = T;

void main()
{
    TypeTuple!(int, int) values;
    auto values2 = cast(long)values;

    // キャストによる型の変換が行われていない.
    static assert (!is(typeof(values2) == long));
    static assert (is(typeof(values2) == TypeTuple!(int, int)));

    // キャストしたtupleが即座に展開されたときのみ変換が行われる.
    auto arr = [cast(long)values];
    static assert (is(typeof(arr) == long[]));


    // サイズが1のtupleのキャストにかんしては特別に要素を取り出して
    // キャスト操作が行われるのでこの問題を気にする必要はない.
    TypeTuple!int i;
    auto i2 = cast(long) i;  // int型の値に対するキャストと同様
    static assert(is(typeof(i2) == long));
}

このへんのロジック、DMDがセルフホストされる以前からこうなっており(少なくとも2015年にはこうなっている)その後大きく変更されていない。 おそらく誰もタプル型のキャストをしようという人がいなかったためこのまま放置されてきたのだろう。

追記

正確には型変換が行われていないというわけではなく、型情報の更新が起きていないという事象になる。

import std.stdio;

alias TypeTuple(T...) = T;

void main()
{
    TypeTuple!(int, int) values;
    values[0] = byte.max + 1;
    writeln(values[0]); // 128

    auto values2 = cast(byte)values;
    // 内部的にはタプルの各要素に対してそれぞれキャストが行われている.
    // ただし型情報の書き換えは行われない(なので(byte,byte)にもならない).
    pragma(msg, typeof(values2)); // (int, int)
    writeln(values2[0]); // -128
}