is Expression - D Programming Language
Description: D Programming Language
Keywords: D programming language

Menu

- Learn
- Documentation
  - Language Reference
  - Library Reference
  - Command-line Reference
  - Feature Overview
  - Articles
- Downloads
- Packages
- Community
  - Blog
  - Orgs using D
  - Twitter
  - Calendar
  - Discord (community-run)
  - Forums
  - IRC
  - Wiki
  - Issues
  - Contributing
  - Contributors
  - Foundation
  - Security Team
  - Donate
  - Sponsors
- Resources
  - Tour
  - Books
  - Tutorials
  - Tools
  - Editors
  - IDEs
  - run.dlang.io
  - Visual D
  - Acknowledgments
  - D Style
  - Glossary
  - Sitemap

Search
Report a bug
If you spot a problem with this page, click here to create a GitHub issue.
Improve this page
Quickly fork, edit online, and submit a pull request for this page. Requires a signed-in GitHub account. This works well for small changes. If you'd like to make larger changes you may want to consider using a local clone.

# is Expression

The `is` expression is not related to the `is` operator that we saw in The `null` Value and the `is` Operator chapter, neither syntactically nor semantically:

        a is b            // is operator, which we have seen before

        is (/* ... */)    // is expression

The `is` expression is evaluated at compile time. It produces an `int` value, either 0 or 1 depending on the expression specified in parentheses. Although the expression that it takes is not a logical expression, the `is` expression itself is used as a compile time logical expression. It is especially useful in `static if` conditionals and template constraints.

The condition that it takes is always about types, which must be written in one of several syntaxes.

##### `is (T)`

Determines whether `T` is valid as a type.

It is difficult to come up with examples for this use at this point. We will take advantage of it in later chapters with template parameters.

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

        } else {
            writeln("invalid");
        }

`int` above is a valid type:

    valid

As another example, because `void` is not valid as the key type of an associative array, the `else` block would be enabled below:

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

        } else {
            writeln("invalid");
        }

The output:

    invalid

##### `is (T Alias)`

Works in the same way as the previous syntax. Additionally, defines `Alias` as an alias of `T`:

        static if (is (int NewAlias)) {
            writeln("valid");
            NewAlias var = 42; // int and NewAlias are the same

        } else {
            writeln("invalid");
        }

Such aliases are useful especially in more complex `is` expressions as we will see below.

##### `is (T : OtherT)`

Determines whether `T` can automatically be converted to `OtherT`.

It is used for detecting automatic type conversions which we have seen in the Type Conversions chapter, as well as relationships like "this type is of that type", which we have seen in the Inheritance chapter.

    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)) {
            // If we are here then T can be used as a 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);
    }

When the `myFunction()` template is instantiated for a type that can be used like a `Clock`, then the `tellTime()` member function is called on its parameter. Otherwise, the `else` clause gets compiled:

    This is a Clock; we can tell the time  ← for AlarmClock
    10:00                                  ← for AlarmClock
    This is not a Clock                    ← for int

##### `is (T Alias : OtherT)`

Works in the same way as the previous syntax. Additionally, defines `Alias` as an alias of `T`.

##### `is (T == Specifier)`

Determines whether `T` is the same type as `Specifier` or whether `T` matches that specifier.

###### Whether the same type

When we change the previous example to use `==` instead of `:`, the condition would not be satisfied for `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");
        }

Although `AlarmClock` is a `Clock`, it is not exactly the same type as `Clock`. For that reason, now the condition is invalid for both `AlarmClock` and `int`:

    This is not a Clock
    This is not a Clock

###### Whether matches the same specifier

When `Specifier` is one of the following keywords, this use of `is` determines whether the type matches that specifier (we will see some of these keywords in later chapters):

- `struct`
- `union`
- `class`
- `interface`
- `enum`
- `function`
- `delegate`
- `const`
- `immutable`
- `shared`

    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");
        }
    }

Function templates can take advantage of such information to behave differently depending on the type that the template is instantiated with. The following code demonstrates how different blocks of the template above get compiled for different types:

        auto var = new AlarmClock;
        myFunction(var);

        // (enum WeekDays will be defined below for another example)
        myFunction(WeekDays.Monday);

        const double number = 1.2;
        myFunction(number);

        myFunction(42);

The output:

    This is a class type
    This is an enum type
    This is a const type
    This is some other type

##### `is (T identifier == Specifier)`

Works in the same way as the previous syntax. `identifier` is either an alias of the type; or some other information depending on `Specifier`:

`Specifier` | The meaning of `identifier`
--- | ---
`struct` | alias of the type that satisfied the condition
`union` | alias of the type that satisfied the condition
`class` | alias of the type that satisfied the condition
`interface` | alias of the type that satisfied the condition
`super` | a tuple consisting of the base classes and the interfaces
`enum` | the actual implementation type of the `enum`
`function` | a tuple consisting of the function parameters
`delegate` | the type of the `delegate`
`return` | the return type of the regular function, the `delegate`, or the function pointer
`__parameters` | a tuple consisting of the parameters of the regular function, the `delegate`, or the function pointer
`const` | alias of the type that satisfied the condition
`immutable` | alias of the type that satisfied the condition
`shared` | alias of the type that satisfied the condition

Let's first define various types before experimenting with this syntax:

    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';
    }

The following function template uses different specifiers with this syntax of the `is` expression:

    void myFunction(T)(T parameter) {
        static if (is (T LocalAlias == struct)) {
            writefln("\n-- struct --");
            // LocalAlias is the same as T. 'parameter' is the
            // struct object that has been passed to this
            // function.

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

        static if (is (T baseTypes == super)) {
            writeln("\n-- super --");
            // The 'baseTypes' tuple contains all of the base
            // types of T. 'parameter' is the class variable that
            // has been passed to this function.

            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' is the actual implementation type of this
            //  enum type. 'parameter' is the enum value that has
            //  been passed to this function.

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

        static if (is (T ReturnT == return)) {
            writeln("\n-- return --");
            // 'ReturnT' is the return type of the function
            // pointer that has been passed to this function.

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

            // Note: Function pointers can be called like
            // functions
            ReturnT result = parameter(1.5, 42, new AlarmClock);
            writefln("and the result is '%s'", result);
        }
    }

Let's now call that function template with various types that we have defined above:

        // Calling with a struct object
        myFunction(Point());

        // Calling with a class reference
        myFunction(new AlarmClock);

        // Calling with an enum value
        myFunction(WeekDays.Monday);

        // Calling with a function pointer
        myFunction(&foo);

The output:

    -- struct --
    Constructing a new Point object by copying it.

    -- super --
    class AlarmClock has 2 base types.
    All of the bases: (Object, Clock)
    The topmost base: Object

    -- enum --
    The implementation type of enum WeekDays is int

    -- return --
    This is a function with a return type of char:
        char function(double d, int i, Clock c)
    calling it... and the result is 'a'

##### `is (/* ... */ Specifier, TemplateParamList)`

There are four different syntaxes of the `is` expression that uses a template parameter list:

- `is (T : Specifier, TemplateParamList)`
- `is (T == Specifier, TemplateParamList)`
- `is (T identifier : Specifier, TemplateParamList)`
- `is (T identifier == Specifier, TemplateParamList)`

These syntaxes allow for more complex cases.

`identifier`, `Specifier`, `:`, and `==` all have the same meanings as described above.

`TemplateParamList` is both a part of the condition that needs to be satisfied and a facility to define additional aliases if the condition is indeed satisfied. It works in the same way as template type deduction.

As a simple example, let's assume that an `is` expression needs to match associative arrays that have keys of type `string`:

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

That condition can be explained in three parts where the last two are parts of the `TemplateParamList`:

1. If `T` matches the syntax of `Value[Key]`
2. If `Value` is a type
3. If `Key` is `string` (remember template specialization syntax)

Having `Value[Key]` as the `Specifier` requires that `T` is an associative array. Leaving `Value` as is means that it can be any type. Additionally, the key type of the associative array must be `string`. As a result, the previous `is` expression means "if `T` is an associative array where the key type is `string`."

The following program tests that `is` expression with four different types:

    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);
    }

The condition is satisfied only if the key type is `string`:

    -- Called with int --
    No, the condition has not been satisfied.

    -- Called with int[string] --
    Yes, the condition has been satisfied.
    The value type: int
    The key type  : string

    -- Called with double[string] --
    Yes, the condition has been satisfied.
    The value type: double
    The key type  : string

    -- Called with dchar[long] --
    No, the condition has not been satisfied.

Copyright © 1999-2026 by the D Language Foundation | Page generated by Ddoc on Wed Aug 19 13:28:51 2026