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:
is表达式与我们在null值和is操作符章节中看到的is操作符没有语法和语义上的关系。
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.
is表达式在编译时计算。它产生一个int值,根据括号中指定的表达式,可以是0或1。虽然它接受的表达式不是逻辑表达式,但is表达式本身被用作编译时逻辑表达式。它在static if条件和模板约束中特别有用。
The condition that it takes is always about types, which must be written in one of several syntaxes.
71.1 is (T)
Determines whether T is valid as a type.
确定
T作为类型是否有效。
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
71.2 is (T Alias)
Works in the same way as the previous syntax. Additionally, defines Alias as an alias of T:
工作方式与前面的语法相同。另外,将
Alias定义为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.
71.3 is (T : OtherT)
Determines whether T can automatically be converted to OtherT.
确定
T是否可以自动转换为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
71.4 is (T Alias : OtherT)
Works in the same way as the previous syntax. Additionally, defines Alias as an alias of T.
工作方式与前面的语法相同。另外,将
Alias定义为T的别名。
71.5 is (T == Specifier)
Determines whether T is the same type as Specifier or whether T matches that specifier.
确定
T是否与Specifier相同类型,或者T是否匹配该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):
当
Specifier是以下关键字之一时,is的使用将确定类型是否匹配该说明符。
structunionclassinterfaceenumfunctiondelegateconstimmutableshared
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
71.6 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的信息。
| 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'
71.7 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
Tmatches the syntax ofValue[Key] - If
Valueis a type - If
Keyisstring(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.