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):
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
:
- If
T
matches the syntax ofValue[Key]
- If
Value
is a type - If
Key
isstring
(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.