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 }
```
Члены именованной группы перечисляемых значений не могут иметь
разные типы; все перечисляемые значения должны иметь один и тот же
тип, поскольку пользователи могут впоследствии определять и исполь
зовать значения этого типа. Например:
```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).