7.7
This commit is contained in:
parent
0e79ca3ab4
commit
7ed9f244ea
|
@ -1991,159 +1991,197 @@ enum
|
||||||
|
|
||||||
Можно дать имя группе перечисляемых значений, создав таким обра
|
Можно дать имя группе перечисляемых значений, создав таким обра
|
||||||
зом новый тип на ее основе:
|
зом новый тип на ее основе:
|
||||||
|
|
||||||
|
```d
|
||||||
enum OddWord { acini, alembicated, prolegomena, aprosexia }
|
enum OddWord { acini, alembicated, prolegomena, aprosexia }
|
||||||
|
```
|
||||||
|
|
||||||
Члены именованной группы перечисляемых значений не могут иметь
|
Члены именованной группы перечисляемых значений не могут иметь
|
||||||
разные типы; все перечисляемые значения должны иметь один и тот же
|
разные типы; все перечисляемые значения должны иметь один и тот же
|
||||||
тип, поскольку пользователи могут впоследствии определять и исполь
|
тип, поскольку пользователи могут впоследствии определять и исполь
|
||||||
зовать значения этого типа. Например:
|
зовать значения этого типа. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
OddWord w;
|
OddWord w;
|
||||||
assert(w == OddWord.acini); // Инициализирующим значением по умолчанию
|
assert(w == OddWord.acini); // Инициализирующим значением по умолчанию является первое значение в множестве - acini.
|
||||||
// является первое значение в множестве - acini.
|
w = OddWord.aprosexia; // Всегда уточняйте имя значения (кстати, это не то, что вы могли подумать) с помощью имени типа.
|
||||||
w = OddWord.aprosexia;
|
int x = w; // OddWord конвертируем в int, но не наоборот.
|
||||||
// Всегда уточняйте имя значения
|
assert(x == 3); // Значения нумеруются по порядку: 0, 1, 2, ...
|
||||||
// (кстати, это не то, что вы могли подумать)
|
```
|
||||||
// с помощью имени типа.
|
|
||||||
int x = w;
|
|
||||||
// OddWord конвертируем в int, но не наоборот.
|
|
||||||
assert(x == 3);
|
|
||||||
// Значения нумеруются по порядку: 0, 1, 2, ...
|
|
||||||
Тип, автоматически определяемый для поименованного перечисления, –
|
Тип, автоматически определяемый для поименованного перечисления, –
|
||||||
int. Присвоить другой тип можно так:
|
`int`. Присвоить другой тип можно так:
|
||||||
|
|
||||||
|
```d
|
||||||
enum OddWord : byte { acini, alembicated, prolegomena, aprosexia }
|
enum OddWord : byte { acini, alembicated, prolegomena, aprosexia }
|
||||||
С новым определением (byte называют базовым типом OddWord) значе
|
```
|
||||||
|
|
||||||
|
С новым определением (`byte` называют *базовым типом* `OddWord`) значе
|
||||||
ния идентификаторов перечисления не меняются, изменяется лишь
|
ния идентификаторов перечисления не меняются, изменяется лишь
|
||||||
способ их хранения. Вы можете с таким же успехом назначить членам
|
способ их хранения. Вы можете с таким же успехом назначить членам
|
||||||
перечисления тип double или real, но связанные с идентификаторами
|
перечисления тип `double` или `real`, но связанные с идентификаторами
|
||||||
значения останутся прежними: 0, 1 и т. д. Но если сделать базовым ти
|
значения останутся прежними: `0`, `1` и т. д. Но если сделать базовым ти
|
||||||
пом OddWord нечисловой тип, например string, то придется указать ини
|
пом `OddWord` нечисловой тип, например `string`, то придется указать ини
|
||||||
циализирующее значение для каждого из значений, поскольку компи
|
циализирующее значение для каждого из значений, поскольку компи
|
||||||
лятору неизвестна никакая естественная последовательность, которой
|
лятору неизвестна никакая естественная последовательность, которой
|
||||||
он мог бы придерживаться.
|
он мог бы придерживаться.
|
||||||
|
|
||||||
Возвратимся к числовым перечислениям. Присвоив какому-либо члену
|
Возвратимся к числовым перечислениям. Присвоив какому-либо члену
|
||||||
перечисления особое значение, вы таким образом сбросите счетчик, ис
|
перечисления особое значение, вы таким образом сбросите счетчик, ис
|
||||||
пользуемый компилятором для присваивания значений идентифика
|
пользуемый компилятором для присваивания значений идентифика
|
||||||
торам. Например:
|
торам. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
enum E { a, b = 2, c, d = -1, e, f }
|
enum E { a, b = 2, c, d = -1, e, f }
|
||||||
assert(E.c == 3);
|
assert(E.c == 3);
|
||||||
assert(E.e == 0);
|
assert(E.e == 0);
|
||||||
|
```
|
||||||
|
|
||||||
Если два идентификатора перечисления получают одно и то же значе
|
Если два идентификатора перечисления получают одно и то же значе
|
||||||
ние (как в случае с E.a и E.e), конфликта нет. Фактически равные значе
|
ние (как в случае с `E.a` и `E.e`), конфликта нет. Фактически равные значе
|
||||||
ния можно создавать, даже не подозревая об этом – из-за непреодолимо
|
ния можно создавать, даже не подозревая об этом – из-за непреодолимо
|
||||||
го желания типов с плавающей запятой удивить небдительных пользо
|
го желания типов с плавающей запятой удивить небдительных пользо
|
||||||
вателей:
|
вателей:
|
||||||
|
|
||||||
|
```d
|
||||||
enum F : float { a = 1E30, b, c, d }
|
enum F : float { a = 1E30, b, c, d }
|
||||||
assert(F.a == F.d); // Тест пройден
|
assert(F.a == F.d); // Тест пройден
|
||||||
Корень этой проблемы в том, что наибольшее значение типа int, кото
|
```
|
||||||
рое может быть представлено значением типа float, равно 16_777_216,
|
|
||||||
и выход за эту границу сопровождается все возрастающими диапазона
|
|
||||||
ми целых значений, представляемых одним и тем же числом типа float.
|
|
||||||
|
|
||||||
7.3.2. Свойства перечисляемых типов
|
Корень этой проблемы в том, что наибольшее значение типа `int`, кото
|
||||||
Для всякого перечисляемого типа E определены три свойства: E.init (это
|
рое может быть представлено значением типа `float`, равно `16_777_216`,
|
||||||
свойство принимает первое из значений, определенных в E), E.min (наи
|
и выход за эту границу сопровождается все возрастающими диапазона
|
||||||
меньшее из определенных в E значений) и E.max (наибольшее из опреде
|
ми целых значений, представляемых одним и тем же числом типа `float`.
|
||||||
ленных в E значений). Два последних значения определены, только ес
|
|
||||||
ли базовым типом E является тип, поддерживающий сравнение во вре
|
### 7.3.2. Свойства перечисляемых типов
|
||||||
мя компиляции с помощью оператора <.
|
|
||||||
Вы вправе определить внутри enum собственные значения min, max и init,
|
Для всякого перечисляемого типа `E` определены три свойства: `E.init` (это
|
||||||
|
свойство принимает первое из значений, определенных в `E`), `E.min` (наи
|
||||||
|
меньшее из определенных в `E` значений) и `E.max` (наибольшее из опреде
|
||||||
|
ленных в `E` значений). Два последних значения определены, только ес
|
||||||
|
ли базовым типом `E` является тип, поддерживающий сравнение во вре
|
||||||
|
мя компиляции с помощью оператора `<`.
|
||||||
|
|
||||||
|
Вы вправе определить внутри `enum` собственные значения `min`, `max` и `init`,
|
||||||
но поступать так не рекомендуется: обобщенный код частенько рассчи
|
но поступать так не рекомендуется: обобщенный код частенько рассчи
|
||||||
тывает на то, что эти значения обладают особой семантикой.
|
тывает на то, что эти значения обладают особой семантикой.
|
||||||
|
|
||||||
Один из часто задаваемых вопросов: «Можно ли добраться до имени пе
|
Один из часто задаваемых вопросов: «Можно ли добраться до имени пе
|
||||||
речисляемого значения?» Вне всяких сомнений, сделать это возможно
|
речисляемого значения?» Вне всяких сомнений, сделать это возможно
|
||||||
и на самом деле легко, но не с помощью встроенного механизма, а на ос
|
и на самом деле легко, но не с помощью встроенного механизма, а на ос
|
||||||
нове рефлексии времени компиляции. Рефлексия работает так: с неко
|
нове рефлексии времени компиляции. Рефлексия работает так: с неко
|
||||||
торым перечисляемым типом Enum связывается известная во время ком
|
торым перечисляемым типом `Enum` связывается известная во время ком
|
||||||
пиляции константа __traits(allMembers, Enum), которая содержит все чле
|
пиляции константа `__traits(allMembers, Enum)`, которая содержит все чле
|
||||||
ны Enum в виде кортежа значений типа string. Поскольку строками мож
|
ны `Enum` в виде кортежа значений типа `string`. Поскольку строками мож
|
||||||
но манипулировать во время компиляции, как и во время исполнения,
|
но манипулировать во время компиляции, как и во время исполнения,
|
||||||
такой подход дает значительную гибкость. Например, немного забежав
|
такой подход дает значительную гибкость. Например, немного забежав
|
||||||
вперед, напишем функцию toString, которая возвращает строку, соот
|
вперед, напишем функцию `toString`, которая возвращает строку, соот
|
||||||
ветствующую заданному перечисляемому значению. Функция парамет
|
ветствующую заданному перечисляемому значению. Функция парамет
|
||||||
ризирована перечисляемым типом.
|
ризирована перечисляемым типом.
|
||||||
string toString(E)(E value) if (is(E == enum)) {
|
|
||||||
foreach (s; __traits(allMembers, E)) {
|
```d
|
||||||
if (value == mixin("E." ~ s)) return s;
|
string toString(E)(E value) if (is(E == enum))
|
||||||
}
|
{
|
||||||
return null;
|
foreach (s; __traits(allMembers, E))
|
||||||
|
{
|
||||||
|
if (value == mixin("E." ~ s)) return s;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
enum OddWord { acini, alembicated, prolegomena, aprosexia }
|
enum OddWord { acini, alembicated, prolegomena, aprosexia }
|
||||||
void main() {
|
|
||||||
auto w = OddWord.alembicated;
|
void main()
|
||||||
assert(toString(w) == "alembicated");
|
{
|
||||||
|
auto w = OddWord.alembicated;
|
||||||
|
assert(toString(w) == "alembicated");
|
||||||
}
|
}
|
||||||
Незнакомое пока выражение mixin("E." ~ s) – это выражение mixin. Вы
|
```
|
||||||
ражение mixin принимает строку, известную во время компиляции,
|
|
||||||
|
Незнакомое пока выражение `mixin("E." ~ s)` – это *выражение* `mixin`. Вы
|
||||||
|
ражение `mixin` принимает строку, известную во время компиляции,
|
||||||
и просто вычисляет ее как обычное выражение в рамках текущего кон
|
и просто вычисляет ее как обычное выражение в рамках текущего кон
|
||||||
текста. В нашем примере это выражение включает имя перечисления E,
|
текста. В нашем примере это выражение включает имя перечисления `E`,
|
||||||
оператор . для выбора внутренних элементов и переменную s для пере
|
оператор . для выбора внутренних элементов и переменную `s` для пере
|
||||||
бора идентификаторов перечисляемых значений. В данном случае s по
|
бора идентификаторов перечисляемых значений. В данном случае s по
|
||||||
следовательно принимает значения "acini", "alembicated", …, "aprosexia".
|
следовательно принимает значения `"acini"`, `"alembicated"`, ..., `"aprosexia"`.
|
||||||
Таким образом, конкатенированная строка примет вид "E.acini" и т. д.,
|
Таким образом, конкатенированная строка примет вид `"E.acini"` и т. д.,
|
||||||
а выражение mixin вычислит ее, сопоставив указанным идентификато
|
а выражение `mixin` вычислит ее, сопоставив указанным идентификато
|
||||||
рам реальные значения. Обнаружив, что переданное значение равно оче
|
рам реальные значения. Обнаружив, что переданное значение равно оче
|
||||||
редному значению, вычисленному выражением mixin, функция toString
|
редному значению, вычисленному выражением `mixin`, функция `toString`
|
||||||
возвращает результат. Получив некорректный аргумент value, функ
|
возвращает результат. Получив некорректный аргумент value, функ
|
||||||
ция toString могла бы порождать исключение, но чтобы упростить себе
|
ция `toString` могла бы порождать исключение, но чтобы упростить себе
|
||||||
жизнь, мы решили просто возвращать константу null.
|
жизнь, мы решили просто возвращать константу `null`.
|
||||||
Рассмотренная функция toString уже реализована в модуле std.conv
|
|
||||||
|
Рассмотренная функция `toString` уже реализована в модуле `std.conv`
|
||||||
стандартной библиотеки, имеющем дело с общими преобразования
|
стандартной библиотеки, имеющем дело с общими преобразования
|
||||||
ми. Имя этой функции немного отличается от того, что использовали
|
ми. Имя этой функции немного отличается от того, что использовали
|
||||||
мы: вам придется писать to!string(w) вместо toString(w), что говорит
|
мы: вам придется писать `to!string(w)` вместо `toString(w)`, что говорит
|
||||||
о гибкости этой функции (также можно сделать вызов to!dstring(w) или
|
о гибкости этой функции (также можно сделать вызов `to!dstring(w)` или
|
||||||
to!byte(w) и т. д.). Этот же модуль определяет и обратную функцию, ко
|
`to!byte(w)` и т. д.). Этот же модуль определяет и обратную функцию, ко
|
||||||
торая конвертирует строку в значение перечисляемого типа; например
|
торая конвертирует строку в значение перечисляемого типа; например
|
||||||
вызов to!OddWord("acini") возвращает OddWord.acini.
|
вызов `to!OddWord("acini")` возвращает `OddWord.acini`.
|
||||||
|
|
||||||
7.4. alias
|
## 7.4. alias
|
||||||
В ряде случаев мы уже имели дело с size_t – целым типом без знака,
|
|
||||||
|
В ряде случаев мы уже имели дело с `size_t` – целым типом без знака,
|
||||||
достаточно вместительным, чтобы представить размер любого объекта.
|
достаточно вместительным, чтобы представить размер любого объекта.
|
||||||
Тип size_t не определен языком, он просто принимает форму uint или
|
Тип `size_t` не определен языком, он просто принимает форму `uint` или
|
||||||
ulong в зависимости от адресного пространства конечной системы (32
|
`ulong` в зависимости от адресного пространства конечной системы (32
|
||||||
или 64 бита соответственно).
|
или 64 бита соответственно).
|
||||||
|
|
||||||
Если бы вы открыли файл object.di, один из копируемых на компьютер
|
Если бы вы открыли файл object.di, один из копируемых на компьютер
|
||||||
пользователя (а значит, и на ваш) при инсталляции компилятора D, то
|
пользователя (а значит, и на ваш) при инсталляции компилятора D, то
|
||||||
нашли бы объявление примерно следующего вида:
|
нашли бы объявление примерно следующего вида:
|
||||||
|
|
||||||
|
```d
|
||||||
alias typeof(int.sizeof) size_t;
|
alias typeof(int.sizeof) size_t;
|
||||||
Свойство .sizeof точно измеряет размер типа в байтах; в данном случае
|
```
|
||||||
это тип int. Вместо int в примере мог быть любой другой тип; в данном
|
|
||||||
|
Свойство `.sizeof` точно измеряет размер типа в байтах; в данном случае
|
||||||
|
это тип `int`. Вместо `int` в примере мог быть любой другой тип; в данном
|
||||||
случае имеет значение не указанный тип, а тип размера, возвращаемый
|
случае имеет значение не указанный тип, а тип размера, возвращаемый
|
||||||
оператором typeof. Компилятор измеряет размеры объектов, используя
|
оператором `typeof`. Компилятор измеряет размеры объектов, используя
|
||||||
uint на 32-разрядных архитектурах и ulong на 64-разрядных. Следова
|
`uint` на 32-разрядных архитектурах и `ulong` на 64-разрядных. Следова
|
||||||
тельно, конструкция alias позволяет назначить size_t синонимом uint
|
тельно, конструкция `alias` позволяет назначить `size_t` синонимом `uint`
|
||||||
или ulong.
|
или `ulong`.
|
||||||
Обобщенный синтаксис объявления с ключевым словом alias ничуть не
|
|
||||||
|
Обобщенный синтаксис объявления с ключевым словом `alias` ничуть не
|
||||||
сложнее приведенного выше:
|
сложнее приведенного выше:
|
||||||
|
|
||||||
|
```d
|
||||||
alias ‹существующийИдентификатор› ‹новыйИдентификатор›;
|
alias ‹существующийИдентификатор› ‹новыйИдентификатор›;
|
||||||
В качестве идентификатора ‹существующийИдентификатор› можно подста
|
```
|
||||||
|
|
||||||
|
В качестве идентификатора `‹существующийИдентификатор›` можно подста
|
||||||
вить все, у чего есть имя. Это может быть тип, переменная, модуль – ес
|
вить все, у чего есть имя. Это может быть тип, переменная, модуль – ес
|
||||||
ли что-то обладает идентификатором, то для этого объекта можно соз
|
ли что-то обладает идентификатором, то для этого объекта можно соз
|
||||||
дать псевдоним. Например:
|
дать псевдоним. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
void fun(int) {}
|
void fun(int) {}
|
||||||
void fun(string) {}
|
void fun(string) {}
|
||||||
int var;
|
int var;
|
||||||
enum E { e }
|
enum E { e }
|
||||||
struct S { int x; }
|
struct S { int x; }
|
||||||
S s;
|
S s;
|
||||||
unittest {
|
|
||||||
alias object.Object Root; // Предок всех классов
|
unittest
|
||||||
alias std phobos;
|
{
|
||||||
// Имя пакета
|
alias object.Object Root; // Предок всех классов
|
||||||
alias std.stdio io;
|
alias std phobos; // Имя пакета
|
||||||
// Имя модуля
|
alias std.stdio io; // Имя модуля
|
||||||
alias var sameAsVar;
|
alias var sameAsVar; // Переменная
|
||||||
// Переменная
|
alias E MyEnum; // Перечисляемый тип
|
||||||
alias E MyEnum;
|
alias E.e myEnumValue; // Значение этого типа
|
||||||
// Перечисляемый тип
|
alias fun gun; // Перегруженная функция
|
||||||
alias E.e myEnumValue;
|
alias S.x field; // Поле структуры
|
||||||
// Значение этого типа
|
alias s.x sfield; // Поле объекта
|
||||||
alias fun gun;
|
|
||||||
// Перегруженная функция
|
|
||||||
alias S.x field;
|
|
||||||
// Поле структуры
|
|
||||||
alias s.x sfield;
|
|
||||||
// Поле объекта
|
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Правила применения псевдонима просты: используйте псевдоним вез
|
Правила применения псевдонима просты: используйте псевдоним вез
|
||||||
де, где допустимо использовать исходный идентификатор. Именно это
|
де, где допустимо использовать исходный идентификатор. Именно это
|
||||||
делает компилятор, но с точностью до наоборот: он с пониманием заме
|
делает компилятор, но с точностью до наоборот: он с пониманием заме
|
||||||
|
@ -2151,54 +2189,74 @@ alias s.x sfield;
|
||||||
же сообщения об ошибках и отлаживаемая программа могут «видеть
|
же сообщения об ошибках и отлаживаемая программа могут «видеть
|
||||||
сквозь» псевдонимы и показывать исходные идентификаторы, что мо
|
сквозь» псевдонимы и показывать исходные идентификаторы, что мо
|
||||||
жет оказаться неожиданным. Например, в некоторых сообщениях об
|
жет оказаться неожиданным. Например, в некоторых сообщениях об
|
||||||
ошибках или в отладочных символах можно увидеть immutable(char)[]
|
ошибках или в отладочных символах можно увидеть `immutable(char)[]`
|
||||||
вместо string. Но что именно будет показано, зависит от реализации
|
вместо `string`. Но что именно будет показано, зависит от реализации
|
||||||
компилятора.
|
компилятора.
|
||||||
С помощью конструкции alias можно создавать псевдонимы псевдони
|
|
||||||
|
С помощью конструкции `alias` можно создавать псевдонимы псевдони
|
||||||
мов для идентификаторов, уже имеющих псевдонимы. Например:
|
мов для идентификаторов, уже имеющих псевдонимы. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
alias int Int;
|
alias int Int;
|
||||||
alias Int MyInt;
|
alias Int MyInt;
|
||||||
|
```
|
||||||
|
|
||||||
Здесь нет ничего особенного, просто следование обычным правилам:
|
Здесь нет ничего особенного, просто следование обычным правилам:
|
||||||
к моменту определения псевдонима MyInt псевдоним Int уже будет заме
|
к моменту определения псевдонима `MyInt` псевдоним `Int` уже будет заме
|
||||||
нен исходным идентификатором int, для которого Int является псевдо
|
нен исходным идентификатором `int`, для которого `Int` является псевдо
|
||||||
нимом.
|
нимом.
|
||||||
Конструкцию alias часто применяют, когда требуется дать сложной це
|
|
||||||
|
Конструкцию `alias` часто применяют, когда требуется дать сложной це
|
||||||
почке идентификаторов более короткое имя или в связке с перегружен
|
почке идентификаторов более короткое имя или в связке с перегружен
|
||||||
ными функциями из разных модулей (см. раздел 5.5.2).
|
ными функциями из разных модулей (см. раздел 5.5.2).
|
||||||
Также конструкцию alias часто используют с параметризированными
|
|
||||||
|
Также конструкцию `alias` часто используют с параметризированными
|
||||||
структурами и классами. Например:
|
структурами и классами. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
// Определить класс-контейнер
|
// Определить класс-контейнер
|
||||||
class Container(T) {
|
class Container(T)
|
||||||
alias T ElementType;
|
{
|
||||||
...
|
alias T ElementType;
|
||||||
|
...
|
||||||
}
|
}
|
||||||
unittest {
|
unittest
|
||||||
Container!int container;
|
{
|
||||||
Container!int.ElementType element;
|
Container!int container;
|
||||||
...
|
Container!int.ElementType element;
|
||||||
|
...
|
||||||
}
|
}
|
||||||
Здесь общедоступный псевдоним ElementType, созданный классом Con
|
```
|
||||||
tainer, – единственный разумный способ обратиться из внешнего мира
|
|
||||||
к аргументу, привязанному к параметру T класса Container. Идентифи
|
Здесь общедоступный псевдоним `ElementType`, созданный классом `Container`, – единственный разумный способ обратиться из внешнего мира
|
||||||
катор T видим лишь внутри определения класса Container, но не снару
|
к аргументу, привязанному к параметру `T` класса `Container`. Идентифи
|
||||||
жи: выражение Container!int.T не компилируется.
|
катор `T` видим лишь внутри определения класса `Container`, но не снару
|
||||||
Наконец, конструкция alias весьма полезна в сочетании с конструкци
|
жи: выражение `Container!int.T` не компилируется.
|
||||||
ей static if. Например:
|
|
||||||
|
Наконец, конструкция `alias` весьма полезна в сочетании с конструкци
|
||||||
|
ей `static if`. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
// Из файла object.di
|
// Из файла object.di
|
||||||
// Определить тип разности между двумя указателями
|
// Определить тип разности между двумя указателями
|
||||||
static if (size_t.sizeof == 4) {
|
static if (size_t.sizeof == 4)
|
||||||
alias int ptrdiff_t;
|
{
|
||||||
} else {
|
alias int ptrdiff_t;
|
||||||
alias long ptrdiff_t;
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias long ptrdiff_t;
|
||||||
}
|
}
|
||||||
// Использовать ptrdiff_t ...
|
// Использовать ptrdiff_t ...
|
||||||
С помощью объявления псевдоним ptrdiff_t привязывается к разным ти
|
```
|
||||||
|
|
||||||
|
С помощью объявления псевдоним `ptrdiff_t` привязывается к разным ти
|
||||||
пам в зависимости от того, по какой ветке статического условия пойдет
|
пам в зависимости от того, по какой ветке статического условия пойдет
|
||||||
поток управления. Без этой возможности привязки код, которому потре
|
поток управления. Без этой возможности привязки код, которому потре
|
||||||
бовался такой тип, пришлось бы разместить в одной из веток static if.
|
бовался такой тип, пришлось бы разместить в одной из веток `static if`.
|
||||||
|
|
||||||
|
## 7.5. Параметризированные контексты (конструкция template)
|
||||||
|
|
||||||
7.5. Параметризированные контексты
|
|
||||||
(конструкция template)
|
|
||||||
Мы уже рассмотрели средства, облегчающие параметризацию во время
|
Мы уже рассмотрели средства, облегчающие параметризацию во время
|
||||||
компиляции (эти средства сродни шаблонам из C++ и родовым типам из
|
компиляции (эти средства сродни шаблонам из C++ и родовым типам из
|
||||||
языков Java и C#), – это функции (см. раздел 5.3), параметризирован
|
языков Java и C#), – это функции (см. раздел 5.3), параметризирован
|
||||||
|
@ -2211,332 +2269,445 @@ alias long ptrdiff_t;
|
||||||
статически известного логического условия. При этом не определяется
|
статически известного логического условия. При этом не определяется
|
||||||
никакой новый тип и не вызывается никакая функция – лишь создает
|
никакой новый тип и не вызывается никакая функция – лишь создает
|
||||||
ся псевдоним для одного из существующих типов.
|
ся псевдоним для одного из существующих типов.
|
||||||
|
|
||||||
Для случаев, когда требуется организовать параметризацию во время
|
Для случаев, когда требуется организовать параметризацию во время
|
||||||
компиляции без определения нового типа или функции, D предоставля
|
компиляции без определения нового типа или функции, D предоставля
|
||||||
ет параметризированные контексты. Такой параметризированный кон
|
ет параметризированные контексты. Такой параметризированный кон
|
||||||
текст вводится следующим образом:
|
текст вводится следующим образом:
|
||||||
template Select(bool cond, T1, T2) {
|
|
||||||
...
|
```d
|
||||||
|
template Select(bool cond, T1, T2)
|
||||||
|
{
|
||||||
|
...
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Этот код – на самом деле лишь каркас для только что упомянутого меха
|
Этот код – на самом деле лишь каркас для только что упомянутого меха
|
||||||
низма выбора во время компиляции. Скоро мы доберемся и до реализа
|
низма выбора во время компиляции. Скоро мы доберемся и до реализа
|
||||||
ции, а пока сосредоточимся на порядке объявления. Объявление с клю
|
ции, а пока сосредоточимся на порядке объявления. Объявление с клю
|
||||||
чевым словом template вводит именованный контекст (в данном случае
|
чевым словом `template` вводит именованный контекст (в данном случае
|
||||||
это Select) с параметрами, вычисляемыми во время компиляции (в дан
|
это `Select`) с параметрами, вычисляемыми во время компиляции (в дан
|
||||||
ном случае это логическое значение и два типа). Объявить контекст
|
ном случае это логическое значение и два типа). Объявить контекст
|
||||||
можно на уровне модуля, внутри определения класса, внутри определе
|
можно на уровне модуля, внутри определения класса, внутри определе
|
||||||
ния структуры, внутри любого другого объявления контекста, но не
|
ния структуры, внутри любого другого объявления контекста, но не
|
||||||
внутри определения функции.
|
внутри определения функции.
|
||||||
|
|
||||||
В теле параметризированного контекста разрешается использовать все
|
В теле параметризированного контекста разрешается использовать все
|
||||||
те же объявления, что и обычно, кроме того, могут быть использованы
|
те же объявления, что и обычно, кроме того, могут быть использованы
|
||||||
параметры контекста. Доступ к любому объявлению контекста можно
|
параметры контекста. Доступ к любому объявлению контекста можно
|
||||||
получить извне, расположив перед его именем имя контекста и ., на
|
получить извне, расположив перед его именем имя контекста и ., на
|
||||||
пример: Select!(true, int, double).foo. Давайте прямо сейчас закончим
|
пример: `Select!(true, int, double).foo`. Давайте прямо сейчас закончим
|
||||||
определение контекста Select, чтобы можно было поиграть с ним:
|
определение контекста `Select`, чтобы можно было поиграть с ним:
|
||||||
template Select(bool cond, T1, T2) {
|
|
||||||
static if (cond) {
|
```d
|
||||||
alias T1 Type;
|
template Select(bool cond, T1, T2)
|
||||||
} else {
|
{
|
||||||
alias T2 Type;
|
static if (cond)
|
||||||
|
{
|
||||||
|
alias T1 Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias T2 Type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
alias Select!(false, int, string).Type MyType;
|
||||||
|
static assert(is(MyType == string));
|
||||||
}
|
}
|
||||||
unittest {
|
```
|
||||||
alias Select!(false, int, string).Type MyType;
|
|
||||||
static assert(is(MyType == string));
|
|
||||||
}
|
|
||||||
Заметим, что тот же результат мы могли бы получить на основе струк
|
Заметим, что тот же результат мы могли бы получить на основе струк
|
||||||
туры или класса, поскольку эти типы могут определять в качестве сво
|
туры или класса, поскольку эти типы могут определять в качестве сво
|
||||||
их внутренних элементов псевдонимы, доступные с помощью обычного
|
их внутренних элементов псевдонимы, доступные с помощью обычного
|
||||||
синтаксиса с оператором . (точка):
|
синтаксиса с оператором `.` (точка):
|
||||||
struct /* или class */ Select2(bool cond, T1, T2) {
|
|
||||||
static if (cond) {
|
```d
|
||||||
alias T1 Type;
|
struct /* или class */ Select2(bool cond, T1, T2)
|
||||||
} else {
|
{
|
||||||
alias T2 Type;
|
static if (cond)
|
||||||
|
{
|
||||||
|
alias T1 Type;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
alias T2 Type;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
unittest
|
||||||
|
{
|
||||||
|
alias Select2!(false, int, string).Type MyType;
|
||||||
|
static assert(is(MyType == string));
|
||||||
}
|
}
|
||||||
unittest {
|
```
|
||||||
alias Select2!(false, int, string).Type MyType;
|
|
||||||
static assert(is(MyType == string));
|
|
||||||
}
|
|
||||||
Согласитесь, такое решение выглядит не очень привлекательно. К при
|
Согласитесь, такое решение выглядит не очень привлекательно. К при
|
||||||
меру, для Select2 в документации пришлось бы написать: «Не создавай
|
меру, для `Select2` в документации пришлось бы написать: «Не создавай
|
||||||
те объекты типа Select2! Он определен только ради псевдонима внутри
|
те объекты типа `Select2`! Он определен только ради псевдонима внутри
|
||||||
него!» Доступный специализированный механизм определения пара
|
него!» Доступный специализированный механизм определения пара
|
||||||
метризированных контекстов позволяет избежать двусмысленности на
|
метризированных контекстов позволяет избежать двусмысленности на
|
||||||
мерений, не вызывает недоумения и исключает возможность некоррект
|
мерений, не вызывает недоумения и исключает возможность некоррект
|
||||||
ного использования.
|
ного использования.
|
||||||
В контексте, определенном с ключевым словом template, можно объяв
|
|
||||||
|
В контексте, определенном с ключевым словом `template`, можно объяв
|
||||||
лять не только псевдонимы – там могут присутствовать самые разные
|
лять не только псевдонимы – там могут присутствовать самые разные
|
||||||
объявления. Определим еще один полезный шаблон. На этот раз это бу
|
объявления. Определим еще один полезный шаблон. На этот раз это бу
|
||||||
дет шаблон, возвращающий логическое значение, которое сообщает, яв
|
дет шаблон, возвращающий логическое значение, которое сообщает, яв
|
||||||
ляется ли заданный тип строкой (в любой кодировке):
|
ляется ли заданный тип строкой (в любой кодировке):
|
||||||
template isSomeString(T) {
|
|
||||||
enum bool value = is(T : const(char[]))
|
```d
|
||||||
|| is(T : const(wchar[])) || is(T : const(dchar[]));
|
template isSomeString(T)
|
||||||
|
{
|
||||||
|
enum bool value = is(T : const(char[])) || is(T : const(wchar[])) || is(T : const(dchar[]));
|
||||||
}
|
}
|
||||||
unittest {
|
|
||||||
// Не строки
|
unittest
|
||||||
static assert(!isSomeString!(int).value);
|
{
|
||||||
static assert(!isSomeString!(byte[]).value);
|
// Не строки
|
||||||
// Строки
|
static assert(!isSomeString!(int).value);
|
||||||
static assert(isSomeString!(char[]).value);
|
static assert(!isSomeString!(byte[]).value);
|
||||||
static assert(isSomeString!(dchar[]).value);
|
// Строки
|
||||||
static assert(isSomeString!(string).value);
|
static assert(isSomeString!(char[]).value);
|
||||||
static assert(isSomeString!(wstring).value);
|
static assert(isSomeString!(dchar[]).value);
|
||||||
static assert(isSomeString!(dstring).value);
|
static assert(isSomeString!(string).value);
|
||||||
static assert(isSomeString!(char[4]).value);
|
static assert(isSomeString!(wstring).value);
|
||||||
|
static assert(isSomeString!(dstring).value);
|
||||||
|
static assert(isSomeString!(char[4]).value);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Параметризированные контексты могут быть рекурсивными; к приме
|
Параметризированные контексты могут быть рекурсивными; к приме
|
||||||
ру, вот одно из возможных решений задачи с факториалом:
|
ру, вот одно из возможных решений задачи с факториалом:
|
||||||
template factorial(uint n) {
|
|
||||||
static if (n <= 1)
|
```d
|
||||||
enum ulong value = 1;
|
template factorial(uint n)
|
||||||
else
|
{
|
||||||
enum ulong value = factorial!(n - 1).value * n;
|
static if (n <= 1)
|
||||||
|
enum ulong value = 1;
|
||||||
|
else
|
||||||
|
enum ulong value = factorial!(n - 1).value * n;
|
||||||
}
|
}
|
||||||
Несмотря на то что factorial является совершенным функциональным
|
```
|
||||||
|
|
||||||
|
Несмотря на то что `factorial` является совершенным функциональным
|
||||||
определением, в данном случае это не лучший подход. При необходимо
|
определением, в данном случае это не лучший подход. При необходимо
|
||||||
сти вычислять значения во время компиляции, пожалуй, стоило бы
|
сти вычислять значения во время компиляции, пожалуй, стоило бы
|
||||||
воспользоваться механизмом вычислений во время компиляции (см.
|
воспользоваться механизмом вычислений во время компиляции (см.
|
||||||
раздел 5.12). В отличие от приведенного выше шаблона factorial, функ
|
раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функ
|
||||||
ция factorial более гибка, поскольку может вычисляться как во время
|
ция `factorial` более гибка, поскольку может вычисляться как во время
|
||||||
компиляции, так и во время исполнения. Конструкция template больше
|
компиляции, так и во время исполнения. Конструкция `template` больше
|
||||||
всего подходит для манипуляции типами, имеющей место в Select
|
всего подходит для манипуляции типами, имеющей место в `Select`
|
||||||
и isSomeString.
|
и `isSomeString`.
|
||||||
|
|
||||||
7.5.1. Одноименные шаблоны
|
### 7.5.1. Одноименные шаблоны
|
||||||
Конструкция template может определять любое количество идентифи
|
|
||||||
|
Конструкция `template` может определять любое количество идентифи
|
||||||
каторов, но, как видно из предыдущих примеров, нередко в ней опреде
|
каторов, но, как видно из предыдущих примеров, нередко в ней опреде
|
||||||
лен ровно один идентификатор. Обычно шаблон определяется лишь
|
лен ровно один идентификатор. Обычно шаблон определяется лишь
|
||||||
с целью решить единственную задачу и в качестве результата сделать
|
с целью решить единственную задачу и в качестве результата сделать
|
||||||
доступным единственный идентификатор (такой как Type в случае Select
|
доступным единственный идентификатор (такой как `Type` в случае `Select`
|
||||||
или value в случае isSomeString).
|
или `value` в случае `isSomeString`).
|
||||||
|
|
||||||
Необходимость помнить о том, что в конце вызова надо указать этот
|
Необходимость помнить о том, что в конце вызова надо указать этот
|
||||||
идентификатор, и всегда его указывать может раздражать. Многие про
|
идентификатор, и всегда его указывать может раздражать. Многие про
|
||||||
сто забывают добавить в конец .Type, а потом удивляются, почему вызов
|
сто забывают добавить в конец `.Type`, а потом удивляются, почему вызов
|
||||||
Select!(cond, A, B) порождает таинственное сообщение об ошибке.
|
`Select!(cond, A, B)` порождает таинственное сообщение об ошибке.
|
||||||
|
|
||||||
D помогает здесь, определяя правило, известное как фокус с одноимен
|
D помогает здесь, определяя правило, известное как фокус с одноимен
|
||||||
ным шаблоном: если внутри конструкции template определен иденти
|
ным шаблоном: если внутри конструкции `template` определен иденти
|
||||||
фикатор, совпадающий с именем самого шаблона, то при любом после
|
фикатор, совпадающий с именем самого шаблона, то при любом после
|
||||||
дующем использовании имени этого шаблона в его конец будет автома
|
дующем использовании имени этого шаблона в его конец будет автома
|
||||||
тически дописываться одноименный идентификатор. Например:
|
тически дописываться одноименный идентификатор. Например:
|
||||||
template isNumeric(T) {
|
|
||||||
enum bool isNumeric = is(T : long) || is(T : real);
|
```d
|
||||||
|
template isNumeric(T)
|
||||||
|
{
|
||||||
|
enum bool isNumeric = is(T : long) || is(T : real);
|
||||||
}
|
}
|
||||||
unittest {
|
|
||||||
static assert(isNumeric!(int));
|
unittest
|
||||||
static assert(!isNumeric!(char[]));
|
{
|
||||||
|
static assert(isNumeric!(int));
|
||||||
|
static assert(!isNumeric!(char[]));
|
||||||
}
|
}
|
||||||
Если теперь некоторый код использует выражение isNumeric!(T), компи
|
```
|
||||||
лятор в каждом случае автоматически заменит его на isNumeric!(T).is
|
|
||||||
Numeric, чем освободит пользователя от хлопот с добавлением идентифи
|
Если теперь некоторый код использует выражение `isNumeric!(T)`, компи
|
||||||
|
лятор в каждом случае автоматически заменит его на `isNumeric!(T).isNumeric`, чем освободит пользователя от хлопот с добавлением идентифи
|
||||||
катора в конец имени шаблона.
|
катора в конец имени шаблона.
|
||||||
|
|
||||||
Шаблон, проделывающий фокус с «тезками», может определять внутри
|
Шаблон, проделывающий фокус с «тезками», может определять внутри
|
||||||
себя и другие идентификаторы, но они будут попросту недоступны за
|
себя и другие идентификаторы, но они будут попросту недоступны за
|
||||||
пределами этого шаблона. Дело в том, что компилятор заменяет иден
|
пределами этого шаблона. Дело в том, что компилятор заменяет иден
|
||||||
тификаторы на раннем этапе процесса поиска имен. Единственный спо
|
тификаторы на раннем этапе процесса поиска имен. Единственный спо
|
||||||
соб получить доступ к таким идентификаторам – обратиться к ним из
|
соб получить доступ к таким идентификаторам – обратиться к ним из
|
||||||
тела самого шаблона. Например:
|
тела самого шаблона. Например:
|
||||||
template isNumeric(T) {
|
|
||||||
enum bool test1 = is(T : long);
|
```d
|
||||||
enum bool test2 = is(T : real);
|
template isNumeric(T)
|
||||||
enum bool isNumeric = test1 || test2;
|
{
|
||||||
|
enum bool test1 = is(T : long);
|
||||||
|
enum bool test2 = is(T : real);
|
||||||
|
enum bool isNumeric = test1 || test2;
|
||||||
}
|
}
|
||||||
unittest {
|
|
||||||
static assert(isNumeric!(int).test1); // Ошибка!
|
unittest
|
||||||
// Тип bool не определяет свойство test1!
|
{
|
||||||
|
static assert(isNumeric!(int).test1); // Ошибка! Тип bool не определяет свойство test1!
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Это сообщение об ошибке вызвано соблюдением правила об одноимен
|
Это сообщение об ошибке вызвано соблюдением правила об одноимен
|
||||||
ности: перед тем как делать что-либо еще, компилятор расширяет вызов
|
ности: перед тем как делать что-либо еще, компилятор расширяет вызов
|
||||||
isNumeric!(int) до isNumeric!(int).isNumeric. Затем пользовательский код
|
`isNumeric!(int)` до `isNumeric!(int).isNumeric`. Затем пользовательский код
|
||||||
делает попытку заполучить значение isNumeric!(int).isNumeric.test1, что
|
делает попытку заполучить значение `isNumeric!(int).isNumeric.test1`, что
|
||||||
равносильно попытке получить внутренний элемент test1 из логическо
|
равносильно попытке получить внутренний элемент `test1` из логическо
|
||||||
го значения, отсюда и сообщение об ошибке. Короче говоря, используй
|
го значения, отсюда и сообщение об ошибке. Короче говоря, используй
|
||||||
те одноименные шаблоны тогда и только тогда, когда хотите открыть
|
те одноименные шаблоны тогда и только тогда, когда хотите открыть
|
||||||
доступ лишь к одному идентификатору. Этот случай скорее частый, чем
|
доступ лишь к одному идентификатору. Этот случай скорее частый, чем
|
||||||
редкий, поэтому одноименные шаблоны очень популярны и удобны.
|
редкий, поэтому одноименные шаблоны очень популярны и удобны.
|
||||||
|
|
||||||
7.5.2. Параметр шаблона this1
|
### 7.5.2. Параметр шаблона this[^4]
|
||||||
|
|
||||||
Познакомившись с классами и структурами, можно параметризовать
|
Познакомившись с классами и структурами, можно параметризовать
|
||||||
наш обобщенный метод типом неявного аргумента this. Например:
|
наш обобщенный метод типом неявного аргумента `this`. Например:
|
||||||
|
|
||||||
|
```d
|
||||||
class Parent
|
class Parent
|
||||||
{
|
{
|
||||||
static string getName(this T)()
|
static string getName(this T)()
|
||||||
{
|
{
|
||||||
return T.stringof;
|
return T.stringof;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
class Derived1: Parent{}
|
class Derived1: Parent{}
|
||||||
class Derived2: Parent{}
|
class Derived2: Parent{}
|
||||||
|
|
||||||
unittest
|
unittest
|
||||||
{
|
{
|
||||||
assert(Parent.getName() == "Parent");
|
assert(Parent.getName() == "Parent");
|
||||||
assert(Derived1.getName() == "Derived1");
|
assert(Derived1.getName() == "Derived1");
|
||||||
assert(Derived2.getName() == "Derived2");
|
assert(Derived2.getName() == "Derived2");
|
||||||
}
|
}
|
||||||
Параметр шаблона this T предписывает компилятору в теле getName
|
```
|
||||||
считать T псевдонимом typeof(this).
|
|
||||||
|
Параметр шаблона `this T` предписывает компилятору в теле `getName`
|
||||||
|
считать `T` псевдонимом `typeof(this)`.
|
||||||
|
|
||||||
В обычный статический метод класса не передаются никакие скрытые
|
В обычный статический метод класса не передаются никакие скрытые
|
||||||
параметры, поэтому невозможно определить, для какого конкретно
|
параметры, поэтому невозможно определить, для какого конкретно
|
||||||
класса вызван этот метод. В приведенном примере компилятор создает
|
класса вызван этот метод. В приведенном примере компилятор создает
|
||||||
три экземпляра шаблонного метода Parent.getName(this T)(): Parent.get
|
три экземпляра шаблонного метода `Parent.getName(this T)()`: `Parent.getName()`, `Derived1.getName()` и `Derived2.getName()`.
|
||||||
Name(), Derived1.getName() и Derived2.getName().
|
|
||||||
Также параметр this удобен в случае, когда один метод нужно исполь
|
Также параметр `this` удобен в случае, когда один метод нужно исполь
|
||||||
зовать для разных квалификаторов неизменяемости объекта (см. гла
|
зовать для разных квалификаторов неизменяемости объекта (см. гла
|
||||||
ву 8).
|
ву 8).
|
||||||
|
|
||||||
7.6. Инъекции кода с помощью
|
## 7.6. Инъекции кода с помощью конструкции mixin template
|
||||||
конструкции mixin template
|
|
||||||
При некоторых программных решениях приходится добавлять шаблон
|
При некоторых программных решениях приходится добавлять шаблон
|
||||||
ный код (такой как определения данных и методов) в одну или несколь
|
ный код (такой как определения данных и методов) в одну или несколь
|
||||||
ко реализаций классов. К типичным примерам относятся поддержка
|
ко реализаций классов. К типичным примерам относятся поддержка
|
||||||
сериализации, шаблон проектирования «Наблюдатель» [27] и передача
|
сериализации, шаблон проектирования «Наблюдатель» и передача
|
||||||
событий в оконных системах.
|
событий в оконных системах.
|
||||||
|
|
||||||
Для этих целей можно было бы воспользоваться механизмом наследова
|
Для этих целей можно было бы воспользоваться механизмом наследова
|
||||||
ния, но поскольку реализуется лишь одиночное наследование, опреде
|
ния, но поскольку реализуется лишь одиночное наследование, опреде
|
||||||
лить для заданного класса несколько источников шаблонного кода не
|
лить для заданного класса несколько источников шаблонного кода не
|
||||||
возможно. Иногда необходим механизм, позволяющий просто вставить
|
возможно. Иногда необходим механизм, позволяющий просто вставить
|
||||||
в класс некоторый готовый код, вместо того чтобы писать его вручную.
|
в класс некоторый готовый код, вместо того чтобы писать его вручную.
|
||||||
Здесь-то и пригодится конструкция mixin template (шаблон mixin). Стоит
|
|
||||||
|
Здесь-то и пригодится конструкция `mixin template` (шаблон `mixin`). Стоит
|
||||||
отметить, что сейчас это средство в основном экспериментальное. Воз
|
отметить, что сейчас это средство в основном экспериментальное. Воз
|
||||||
можно, в будущих версиях языка шаблоны mixin заменит более общий
|
можно, в будущих версиях языка шаблоны `mixin` заменит более общий
|
||||||
инструмент AST-макросов.
|
инструмент AST-макросов.
|
||||||
Шаблон mixin определяется почти так же, как параметризированный
|
|
||||||
контекст (шаблон), о котором недавно шла речь. Пример шаблона mixin,
|
Шаблон `mixin` определяется почти так же, как параметризированный
|
||||||
|
контекст (шаблон), о котором недавно шла речь. Пример шаблона `mixin`,
|
||||||
определяющего переменную и функции для ее чтения и записи:
|
определяющего переменную и функции для ее чтения и записи:
|
||||||
mixin template InjectX() {
|
|
||||||
private int x;
|
```d
|
||||||
int getX() { return x; }
|
mixin template InjectX()
|
||||||
void setX(int y) {
|
{
|
||||||
... // Проверки
|
private int x;
|
||||||
x = y;
|
int getX() { return x; }
|
||||||
|
void setX(int y)
|
||||||
|
{
|
||||||
|
... // Проверки
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
Определив шаблон mixin, можно вставить его в нескольких местах:
|
|
||||||
|
Определив шаблон `mixin`, можно вставить его в нескольких местах:
|
||||||
|
|
||||||
|
```d
|
||||||
// Сделать инъекцию в контексте модуля
|
// Сделать инъекцию в контексте модуля
|
||||||
mixin InjectX;
|
mixin InjectX;
|
||||||
class A {
|
|
||||||
// Сделать инъекцию в класс
|
class A
|
||||||
mixin InjectX;
|
{
|
||||||
...
|
// Сделать инъекцию в класс
|
||||||
|
mixin InjectX;
|
||||||
|
...
|
||||||
}
|
}
|
||||||
void fun() {
|
|
||||||
// Сделать инъекцию в функцию
|
void fun()
|
||||||
mixin InjectX;
|
{
|
||||||
setX(10);
|
// Сделать инъекцию в функцию
|
||||||
assert(getX() == 10);
|
mixin InjectX;
|
||||||
|
setX(10);
|
||||||
|
assert(getX() == 10);
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Теперь этот код определяет переменную и две обслуживающие ее функ
|
Теперь этот код определяет переменную и две обслуживающие ее функ
|
||||||
ции на уровне модуля, внутри класса A и внутри функции fun – как буд
|
ции на уровне модуля, внутри класса `A` и внутри функции `fun` – как буд
|
||||||
то тело InjectX было вставлено вручную. В частности, потомки класса A
|
то тело `InjectX` было вставлено вручную. В частности, потомки класса A
|
||||||
могут переопределять методы getX и setX, как если бы сам класс опреде
|
могут переопределять методы `getX` и `setX`, как если бы сам класс опреде
|
||||||
лял их. Копирование и вставка без неприятного дублирования кода –
|
лял их. Копирование и вставка без неприятного дублирования кода –
|
||||||
вот что такое mixin template.
|
вот что такое `mixin template`.
|
||||||
Конечно же, следующий логический шаг – подумать о том, что InjectX
|
|
||||||
|
Конечно же, следующий логический шаг – подумать о том, что `InjectX`
|
||||||
не принимает никаких параметров, но производит впечатление, что мог
|
не принимает никаких параметров, но производит впечатление, что мог
|
||||||
бы, – и действительно может:
|
бы, – и действительно может:
|
||||||
mixin template InjectX(T) {
|
|
||||||
private T x;
|
```d
|
||||||
T getX() { return x; }
|
mixin template InjectX(T)
|
||||||
void setX(T y) {
|
{
|
||||||
... // Проверки
|
private T x;
|
||||||
x = y;
|
T getX() { return x; }
|
||||||
|
void setX(T y)
|
||||||
|
{
|
||||||
|
... // Проверки
|
||||||
|
x = y;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
```
|
||||||
Теперь при обращении к InjectX нужно передавать аргумент так:
|
|
||||||
|
Теперь при обращении к `InjectX` нужно передавать аргумент так:
|
||||||
|
|
||||||
|
```d
|
||||||
mixin InjectX!int;
|
mixin InjectX!int;
|
||||||
mixin InjectX!double;
|
mixin InjectX!double;
|
||||||
|
```
|
||||||
|
|
||||||
Но на самом деле такие вставки приводят к двусмысленности: что если
|
Но на самом деле такие вставки приводят к двусмысленности: что если
|
||||||
вы сделаете две рассмотренные подстановки, а затем пожелаете восполь
|
вы сделаете две рассмотренные подстановки, а затем пожелаете восполь
|
||||||
зоваться функцией getX? Есть две функции с этим именем, так что про
|
зоваться функцией `getX`? Есть две функции с этим именем, так что про
|
||||||
блема с двусмысленностью очевидна. Чтобы решить этот вопрос, D по
|
блема с двусмысленностью очевидна. Чтобы решить этот вопрос, D по
|
||||||
зволяет вводить имена для конкретных подстановок в шаблоны mixin:
|
зволяет вводить *имена* для конкретных подстановок в шаблоны `mixin`:
|
||||||
|
|
||||||
|
```d
|
||||||
mixin InjectX!int MyInt;
|
mixin InjectX!int MyInt;
|
||||||
mixin InjectX!double MyDouble;
|
mixin InjectX!double MyDouble;
|
||||||
|
```
|
||||||
|
|
||||||
Задав такие определения, вы можете недвусмысленно обратиться к внут
|
Задав такие определения, вы можете недвусмысленно обратиться к внут
|
||||||
ренним элементам любого из шаблонов mixin, просто указав нужный
|
ренним элементам любого из шаблонов `mixin`, просто указав нужный
|
||||||
контекст:
|
контекст:
|
||||||
|
|
||||||
|
```d
|
||||||
MyInt.setX(5);
|
MyInt.setX(5);
|
||||||
assert(MyInt.getX() == 5);
|
assert(MyInt.getX() == 5);
|
||||||
MyDouble.setX(5.5);
|
MyDouble.setX(5.5);
|
||||||
assert(MyDouble.getX() == 5.5);
|
assert(MyDouble.getX() == 5.5);
|
||||||
Таким образом, шаблоны mixin – это почти как копирование и вставка;
|
```
|
||||||
|
|
||||||
|
Таким образом, шаблоны `mixin` – это *почти* как копирование и вставка;
|
||||||
вы можете многократно копировать и вставлять код, а потом указы
|
вы можете многократно копировать и вставлять код, а потом указы
|
||||||
вать, к какой именно вставке хотите обратиться.
|
вать, к какой именно вставке хотите обратиться.
|
||||||
|
|
||||||
7.6.1. Поиск идентификаторов внутри mixin
|
### 7.6.1. Поиск идентификаторов внутри mixin
|
||||||
Самая большая разница между шаблоном mixin и обычным шаблоном
|
|
||||||
|
Самая большая разница между шаблоном `mixin` и обычным шаблоном
|
||||||
(в том виде, как он определен в разделе 7.5), способная вызвать больше
|
(в том виде, как он определен в разделе 7.5), способная вызвать больше
|
||||||
всего вопросов, – это поиск имен.
|
всего вопросов, – это поиск имен.
|
||||||
|
|
||||||
Шаблоны исключительно модульны: код внутри шаблона ищет иденти
|
Шаблоны исключительно модульны: код внутри шаблона ищет иденти
|
||||||
фикаторы в месте определения шаблона. Это положительное качество:
|
фикаторы в месте *определения* шаблона. Это положительное качество:
|
||||||
оно гарантирует, что, проанализировав определение шаблона, вы уже
|
оно гарантирует, что, проанализировав определение шаблона, вы уже
|
||||||
ясно представляете его содержимое и осознаете, как он работает.
|
ясно представляете его содержимое и осознаете, как он работает.
|
||||||
Шаблон mixin, напротив, ищет идентификаторы в месте подстановки,
|
|
||||||
а это означает, что понять поведение шаблона mixin можно только с уче
|
Шаблон `mixin`, напротив, ищет идентификаторы в месте *подстановки*,
|
||||||
|
а это означает, что понять поведение шаблона `mixin` можно только с уче
|
||||||
том контекста, в котором вы собираетесь этот шаблон использовать.
|
том контекста, в котором вы собираетесь этот шаблон использовать.
|
||||||
|
|
||||||
Чтобы проиллюстрировать разницу, рассмотрим следующий пример,
|
Чтобы проиллюстрировать разницу, рассмотрим следующий пример,
|
||||||
в котором идентификаторы объявляются как в месте определения, так
|
в котором идентификаторы объявляются как в месте определения, так
|
||||||
и в месте подстановки:
|
и в месте подстановки:
|
||||||
|
|
||||||
|
```d
|
||||||
import std.stdio;
|
import std.stdio;
|
||||||
|
|
||||||
string lookMeUp = "Найдено на уровне модуля";
|
string lookMeUp = "Найдено на уровне модуля";
|
||||||
template TestT() {
|
|
||||||
string get() { return lookMeUp; }
|
template TestT()
|
||||||
|
{
|
||||||
|
string get() { return lookMeUp; }
|
||||||
}
|
}
|
||||||
mixin template TestM() {
|
|
||||||
string get() { return lookMeUp; }
|
mixin template TestM()
|
||||||
|
{
|
||||||
|
string get() { return lookMeUp; }
|
||||||
}
|
}
|
||||||
void main() {
|
|
||||||
string lookMeUp = "Найдено на уровне функции";
|
void main()
|
||||||
alias TestT!() asTemplate;
|
{
|
||||||
mixin TestM!() asMixin;
|
string lookMeUp = "Найдено на уровне функции";
|
||||||
writeln(asTemplate.get());
|
alias TestT!() asTemplate;
|
||||||
writeln(asMixin.get());
|
mixin TestM!() asMixin;
|
||||||
|
writeln(asTemplate.get());
|
||||||
|
writeln(asMixin.get());
|
||||||
}
|
}
|
||||||
|
```
|
||||||
|
|
||||||
Эта программа выведет на экран:
|
Эта программа выведет на экран:
|
||||||
Най
|
|
||||||
де
|
```
|
||||||
но на уров
|
Найдено на уровне модуля
|
||||||
не мо
|
Найдено на уровне функции
|
||||||
ду
|
```
|
||||||
ля
|
|
||||||
Най
|
Склонность шаблонов `mixin` привязываться к локальным идентифика
|
||||||
де
|
|
||||||
но на уров
|
|
||||||
не функ
|
|
||||||
ции
|
|
||||||
Склонность шаблонов mixin привязываться к локальным идентифика
|
|
||||||
торам придает им выразительности, но следовать их логике становится
|
торам придает им выразительности, но следовать их логике становится
|
||||||
сложно. Такое поведение делает шаблоны mixin применимыми лишь
|
сложно. Такое поведение делает шаблоны `mixin` применимыми лишь
|
||||||
в ограниченном количестве случаев; прежде чем доставать из ящика
|
в ограниченном количестве случаев; прежде чем доставать из ящика
|
||||||
с инструментами эти особенные ножницы, необходимо семь раз отме
|
с инструментами эти особенные ножницы, необходимо семь раз отме
|
||||||
рить.
|
рить.
|
||||||
|
|
||||||
7.7. Итоги
|
## 7.7. Итоги
|
||||||
|
|
||||||
Классы позволяют эффективно представить далеко не любую абстрак
|
Классы позволяют эффективно представить далеко не любую абстрак
|
||||||
цию. Например, они не подходят для мелкокалиберных объектов, кон
|
цию. Например, они не подходят для мелкокалиберных объектов, кон
|
||||||
текстно-зависимых ресурсов и типов значений. Этот пробел восполня
|
текстно-зависимых ресурсов и типов значений. Этот пробел восполня
|
||||||
ют структуры. В частности, благодаря конструкторам и деструкторам
|
ют структуры. В частности, благодаря конструкторам и деструкторам
|
||||||
легко определять типы контекстно-зависимых ресурсов.
|
легко определять типы контекстно-зависимых ресурсов.
|
||||||
|
|
||||||
Объединения – низкоуровневое средство, позволяющее хранить разные
|
Объединения – низкоуровневое средство, позволяющее хранить разные
|
||||||
типы данных в одной области памяти с перекрыванием.
|
типы данных в одной области памяти с перекрыванием.
|
||||||
|
|
||||||
Перечисления – это обычные отдельные значения, определенные поль
|
Перечисления – это обычные отдельные значения, определенные поль
|
||||||
зователем. Перечислению может быть назначен новый тип, что позво
|
зователем. Перечислению может быть назначен новый тип, что позво
|
||||||
ляет более точно проверять типы значений, определенных в рамках
|
ляет более точно проверять типы значений, определенных в рамках
|
||||||
этого типа.
|
этого типа.
|
||||||
alias – очень полезное средство, позволяющее привязать один иденти
|
|
||||||
|
`alias` – очень полезное средство, позволяющее привязать один иденти
|
||||||
фикатор к другому. Нередко псевдоним – единственное средство полу
|
фикатор к другому. Нередко псевдоним – единственное средство полу
|
||||||
чить извне доступ к идентификатору, вычисляемому в рамках вложен
|
чить извне доступ к идентификатору, вычисляемому в рамках вложен
|
||||||
ной сущности, или к длинному и сложному идентификатору.
|
ной сущности, или к длинному и сложному идентификатору.
|
||||||
Параметризированные контексты, использующие конструкцию templa
|
|
||||||
te, весьма полезны для определения вычислений во время компиля
|
Параметризированные контексты, использующие конструкцию `template`, весьма полезны для определения вычислений во время компиля
|
||||||
ции, таких как интроспекция типов и определение особенностей типов.
|
ции, таких как интроспекция типов и определение особенностей типов.
|
||||||
Одноименные шаблоны позволяют предоставлять абстракции в очень
|
Одноименные шаблоны позволяют предоставлять абстракции в очень
|
||||||
удобной, инкапсулированной форме.
|
удобной, инкапсулированной форме.
|
||||||
|
|
||||||
Кроме того, предлагаются параметризированные контексты, прини
|
Кроме того, предлагаются параметризированные контексты, прини
|
||||||
мающие форму шаблонов mixin, которые во многом ведут себя подобно
|
мающие форму шаблонов `mixin`, которые во многом ведут себя подобно
|
||||||
макросам. В будущем шаблоны mixin может заменить развитое средст
|
макросам. В будущем шаблоны `mixin` может заменить развитое средст
|
||||||
во AST-макросов.
|
во AST-макросов.
|
||||||
|
|
||||||
[^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).
|
[^1]: Не считая эквивалентных имен, создаваемых с помощью `alias`, о чем мы еще поговорим в этой главе (см. раздел 7.4).
|
||||||
|
|
Loading…
Reference in New Issue