|
@@ -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,
|
|
|
+```
|
|
|
+
|
|
|
+Корень этой проблемы в том, что наибольшее значение типа `int`, кото
|
|
|
+рое может быть представлено значением типа `float`, равно `16_777_216`,
|
|
|
и выход за эту границу сопровождается все возрастающими диапазона
|
|
|
-ми целых значений, представляемых одним и тем же числом типа float.
|
|
|
-
|
|
|
-7.3.2. Свойства перечисляемых типов
|
|
|
-Для всякого перечисляемого типа E определены три свойства: E.init (это
|
|
|
-свойство принимает первое из значений, определенных в E), E.min (наи
|
|
|
-меньшее из определенных в E значений) и E.max (наибольшее из опреде
|
|
|
-ленных в E значений). Два последних значения определены, только ес
|
|
|
-ли базовым типом E является тип, поддерживающий сравнение во вре
|
|
|
-мя компиляции с помощью оператора <.
|
|
|
-Вы вправе определить внутри enum собственные значения min, max и init,
|
|
|
+ми целых значений, представляемых одним и тем же числом типа `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
|
|
|
|
|
|
-7.4. alias
|
|
|
-В ряде случаев мы уже имели дело с size_t – целым типом без знака,
|
|
|
+В ряде случаев мы уже имели дело с `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[]));
|
|
|
-}
|
|
|
-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);
|
|
|
+
|
|
|
+```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);
|
|
|
}
|
|
|
+```
|
|
|
+
|
|
|
Параметризированные контексты могут быть рекурсивными; к приме
|
|
|
ру, вот одно из возможных решений задачи с факториалом:
|
|
|
-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.
|
|
|
-
|
|
|
-7.5.1. Одноименные шаблоны
|
|
|
-Конструкция template может определять любое количество идентифи
|
|
|
+раздел 5.12). В отличие от приведенного выше шаблона `factorial`, функ
|
|
|
+ция `factorial` более гибка, поскольку может вычисляться как во время
|
|
|
+компиляции, так и во время исполнения. Конструкция `template` больше
|
|
|
+всего подходит для манипуляции типами, имеющей место в `Select`
|
|
|
+и `isSomeString`.
|
|
|
+
|
|
|
+### 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).
|