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