is

isは、 null値とis演算子の章で見たis演算子とは、構文的にも意味的にも関係がない。

a is b            // isは、これまでに見た演算子である

is (/* ... */)    // is式
D

is式は、コンパイル時に評価される。これは、括弧で指定された式に応じて、0または1のint値を生成する。この式が取る式は論理式ではないが、is式自体は、コンパイル時の論理式として使用される。これは、static if条件式やテンプレート制約で特に有用だ。

この式が取る条件は、常に型に関するもので、いくつかの構文のいずれかで記述する必要がある。

is (T)

Tが型として有効であるかどうかを判断する。

この時点では、この使用例を思いつくことは難しい。後の章で、テンプレートパラメータでこれを利用することにする。

static if (is (int)) {
    writeln("valid");

} else {
    writeln("invalid");
}
D

intは有効な型である。

有効

別の例として、voidは連想配列のキー型としては有効ではないため、以下のelseブロックが有効になる。

static if (is (string[void])) {
    writeln("valid");

} else {
    writeln("invalid");
}
D

出力:

無効
is (T Alias)

以前の構文と同じように動作する。さらに、AliasTの別名として定義する:

static if (is (int NewAlias)) {
    writeln("valid");
    NewAlias var = 42; // intとNewAliasは同じである

} else {
    writeln("invalid");
}
D

このようなエイリアスは、以下で見るような、より複雑なis式で特に役立つ。

is (T : OtherT)

Tが自動的にOtherTに変換できるかどうかを決定する。

これは、型変換の章で見た自動型変換、および継承の章で見た"この型はあの型である"という関係などを検出するために使われる。

import std.stdio;

interface Clock {
    void tellTime();
}

class AlarmClock : Clock {
    override void tellTime() {
        writeln("10:00");
    }
}

void myFunction(T)(T parameter) {
    static if (is (T : Clock)) {
        // ここにいれば、TをClockとして使うことができる
        writeln("This is a Clock; we can tell the time");
        parameter.tellTime();

    } else {
        writeln("This is not a Clock");
    }
}

void main() {
    auto var = new AlarmClock;
    myFunction(var);
    myFunction(42);
}

myFunction()テンプレートが、Clockのように使用できる型に対してインスタンス化されると、そのパラメータに対してtellTime()メンバー関数が呼び出される。それ以外の場合、else節がコンパイルされる。

これはClockである; 時刻を知ることができる  ← AlarmClock用
10:00                                  ← AlarmClock用
これはClockではない                    ← int用
is (T Alias : OtherT)

以前の構文と同じように動作する。さらに、AliasTの別名として定義する。

is (T == 指定子)

T 指定子と同じ型であるかどうか、またはTがその指定子と一致するかどうかを判断する。

同じ型かどうか

前の例を、:の代わりに==を使用するように変更すると、AlarmClockについては条件が満たされなくなる。

static if (is (T == Clock)) {
    writeln("This is a Clock; we can tell the time");
    parameter.tellTime();

} else {
    writeln("This is not a Clock");
}
D

AlarmClock ClockだがClockとまったく同じ型ではないからである。そのため、AlarmClockintの両方で条件が満たされなくなった。

これはClockではない
これはClockではない
同じ指定子と一致するかどうか

指定子が次のキーワードのいずれかである場合、isのこの使用は、型がその指定子と一致するかどうかを判断する(これらのキーワードの一部は、後の章で説明する)。

void myFunction(T)(T parameter) {
    static if (is (T == class)) {
        writeln("This is a class type");

    } else static if (is (T == enum)) {
        writeln("This is an enum type");

    } else static if (is (T == const)) {
        writeln("This is a const type");

    } else {
        writeln("This is some other type");
    }
}
D

関数テンプレートは、このような情報を利用して、テンプレートがインスタンス化される型に応じて異なる動作を行うことができる。次のコードは、上記のテンプレートの異なるブロックが、異なる型に対してどのようにコンパイルされるかを示している。

auto var = new AlarmClock;
myFunction(var);

// (列挙型WeekDaysは、別の例で後で定義する)
myFunction(WeekDays.Monday);

const double number = 1.2;
myFunction(number);

myFunction(42);
D

出力:

これはクラス型である
これは列挙型である
これは定数型である
これはその他の型である
is (T 識別子 == 指定子)

前の構文と同じように動作する。識別子は、型の別名、または指定子に応じてその他の情報になる。

指定子識別子
struct条件を満たした型のエイリアス
union条件を満たす型の別名
class条件を満たす型の別名
interface条件を満たした型の別名
super基底クラスとインターフェースからなるタプル
enumenumの実際の実装型
function関数パラメータで構成されるタプル
delegatedelegateの型
return通常の関数、delegate、または関数ポインタの戻り値の型
__parameters通常の関数のパラメータ、delegate、または関数ポインタで構成されるタプル
const条件を満たす型の別名
immutable条件を満たす型の別名
shared条件を満たす型の別名

この構文を試す前に、まずさまざまな型を定義しておこう。

struct Point {
    // ...
}

interface Clock {
    // ...
}

class AlarmClock : Clock {
    // ...
}

enum WeekDays {
    Monday, Tuesday, Wednesday, Thursday, Friday,
    Saturday, Sunday
}

char foo(double d, int i, Clock c) {
    return 'a';
}
D

次の関数テンプレートは、is式のこの構文で異なる指定子を使っている。

void myFunction(T)(T parameter) {
    static if (is (T LocalAlias == struct)) {
        writefln("\n--- struct ---");
        // LocalAliasはTと同じである。'parameter'は、
        // この関数に引数として渡された
        // 構造体オブジェクトである。

        writefln("Constructing a new %s object by copying it.",
                 LocalAlias.stringof);
        LocalAlias theCopy = parameter;
    }

    static if (is (T baseTypes == super)) {
        writeln("\n--- super ---");
        // 'baseTypes'タプルには、Tのすべての
        // 基本型が含まれる。'parameter'は、
        // この関数に引数として渡されたクラス変数である。

        writefln("class %s has %s base types.",
                 T.stringof, baseTypes.length);

        writeln("All of the bases: ", baseTypes.stringof);
        writeln("The topmost base: ", baseTypes[0].stringof);
    }

    static if (is (T ImplT == enum)) {
        writeln("\n--- enum ---");
        // 'ImplT'はこの列挙型の
        //  実際の実装型である。'parameter'は、
        //  この関数に渡された列挙型の値である。

        writefln("The implementation type of enum %s is %s",
                 T.stringof, ImplT.stringof);
    }

    static if (is (T ReturnT == return)) {
        writeln("\n--- return ---");
        // 'ReturnT'は、この関数に引数として渡された
        // 関数ポインタの戻り値の型である。

        writefln("This is a function with a return type of %s:",
                 ReturnT.stringof);
        writeln("    ", T.stringof);
        write("calling it... ");

        // 注釈: 関数ポインタは関数と同様に呼び出すことが
        // できる
        ReturnT result = parameter(1.5, 42, new AlarmClock);
        writefln("and the result is '%s'", result);
    }
}
D

それでは、上で定義したさまざまな型で、この関数テンプレートを呼び出そう。

// 構造体オブジェクトで呼び出す
myFunction(Point());

// クラス参照で呼び出す
myFunction(new AlarmClock);

// 列挙型値で呼び出す
myFunction(WeekDays.Monday);

// 関数ポインタで呼び出す
myFunction(&foo);
D

出力:

--- struct ---
コピーして新しいPointオブジェクトを構築する。

--- super ---
クラスAlarmClockには2つの基本型がある。
すべての基底型: (Object, Clock)
最上位の基底型: Object

--- enum ---
列挙型WeekDaysの実装型はint

--- return ---
これは、戻り値の型がcharの関数:
    char function(double d, int i, Clock c)
これを呼び出すと... 結果は'a'になる
is (/* ... */ 指定子, テンプレートのパラメータリスト)

テンプレートパラメータリストを使用するis式には、4つの異なる構文がある。

これらの構文により、より複雑なケースに対応できる。

識別子指定子:、および==は、上記で説明したのと同じ意味を持つ。

テンプレートのパラメータリストは、満たすべき条件の一部であると同時に、条件が実際に満たされた場合に追加のエイリアスを定義するための機能でもある。これは、テンプレート型の推論と同じように機能する。

簡単な例として、is式が、string型のキーを持つ連想配列と一致する必要があると仮定しよう。

static if (is (T == Value[Key],   // (1)
               Value,             // (2)
               Key : string)) {   // (3)
D

この条件は3つの部分で説明でき、最後の2つはテンプレートのパラメータリストの一部である:

  1. TValue[Key]
  2. Valueが型の場合
  3. Keystringの場合(テンプレート特化構文を思い出して)

Value[Key]指定子として使用するには、Tが連想配列である必要がある。Valueをそのままにしておくと、任意の型になる。さらに、連想配列のキーの型はstringでなければならない。その結果、前のis式は、"Tが、キーの型がstringである連想配列である場合"という意味になる。

次のプログラムは、4つの異なる型でis式をテストする。

import std.stdio;

void myFunction(T)(T parameter) {
    writefln("\n--- Called with %s ---", T.stringof);

    static if (is (T == Value[Key],
                   Value,
                   Key : string)) {

        writeln("Yes, the condition has been satisfied.");

        writeln("The value type: ", Value.stringof);
        writeln("The key type  : ", Key.stringof);

    } else {
        writeln("No, the condition has not been satisfied.");
    }
}

void main() {
    int number;
    myFunction(number);

    int[string] intTable;
    myFunction(intTable);

    double[string] doubleTable;
    myFunction(doubleTable);

    dchar[long] dcharTable;
    myFunction(dcharTable);
}

条件は、キーの型がstringの場合にのみ満たされる。

--- intで呼び出された ---
いいえ、条件は満たされていない。

--- int[string]で呼び出された ---
はい、条件は満たされている。
値の型: int
キーの型  : string

--- double[string]で呼び出された ---
はい、条件は満たされている。
値の型: double
キーの型  : string

--- dchar[long]で呼び出された ---
いいえ、条件は満たされていない。