This commit is contained in:
Alexander Zhirov 2023-02-28 09:53:40 +03:00
parent 0e79ca3ab4
commit 7ed9f244ea
1 changed files with 452 additions and 281 deletions

View File

@ -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).