Руководство по Ada (Ada 2022)
3.9.2 Операции диспетчеризации для помеченных типов
Примитивные подпрограммы помеченного типа, подпрограммы, объявленные с помощью formal_abstract_subprogram_declaration, атрибут Put_Image (см. 4.10) определенного помеченного типа и потоковые атрибуты определенного помеченного типа, доступные (см. 13.13.2) в конце списка объявления, где тип объявлен, называются операциями диспетчеризации. Операцию диспетчеризации можно вызвать, используя статически определенный управляющий тег, в этом случае тело для выполнения определяется на этапе компиляции. В качестве альтернативы, управляющий тег может быть определен динамически, в этом случае вызов диспетчеризует выполнение к телу, которое определяется во время выполнения; такой вызов называется вызовом диспетчеризации. Как объяснено ниже, свойства операндов и контекст конкретного вызова операции диспетчеризации определяют, как определяется управляющий тег, а следовательно, является ли вызов вызовом диспетчеризации. Полиморфизм во время выполнения достигается, когда операция диспетчеризации вызывается с помощью вызова диспетчеризации.
Статическая семантика
Вызов операции диспетчеризации — это вызов, имя или префикс которого обозначает объявление операции диспетчеризации. Управляющий операнд в вызове операции диспетчеризации помеченного типа T — это операнд, соответствующий формальному параметру типа T или анонимного типа доступа с обозначенным типом T; соответствующий формальный параметр называется управляющим формальным параметром. Если управляющий формальный параметр является параметром доступа, управляющим операндом является объект, на который указывает фактический параметр, а не сам фактический параметр. Если вызов направлен к (примитивной) функции с типом результата T (функция с управляющим результатом), то вызов имеет управляющий результат — контекст вызова может управлять диспетчеризацией. Аналогично, если вызов направлен к функции с типом результата доступа, обозначающим T (функция с управляющим результатом доступа), то вызов имеет управляющий результат доступа, и контекст аналогичным образом может управлять диспетчеризацией.
Имя или выражение помеченного типа являются либо статически помеченными, динамически помеченными, или неопределёнными по тегу, в зависимости от того, когда они используются в качестве управляющего операнда, тег, управляющий диспетчеризацией, определяется статически типом операнда, динамически тегом во время выполнения или из контекста. Оператор qualified_expression или выражение в скобках являются статически, динамически или неопределённо помеченными в соответствии с операндом. Оператор conditional_expression является статически, динамически или неопределённо помеченным в соответствии с правилами, приведенными в 4.5.7. Оператор declare_expression является статически, динамически или неопределённо помеченным в соответствии с его выражением body_expression. Для других типов имён и выражений это определяется следующим образом:
- Имя или выражение статически помечены, если они имеют тип определенного помеченного типа и, если это вызов с управляющим результатом или управляющим результатом доступа, у него есть по крайней мере один статически помеченный управляющий операнд;
- Имя или выражение динамически помечены, если они имеют тип типа класса, или это вызов с управляющим результатом или управляющим результатом доступа и по крайней мере один динамически помеченный управляющий операнд;
- Имя или выражение неопределены по тегу, если это вызов с управляющим результатом или управляющим результатом доступа, все управляющие операнды (если таковые имеются) неопределены по тегу.
Преобразование типа type_conversion является статически или динамически помеченным в зависимости от того, является ли тип, определяемый subtype_mark, специфическим или типовым для класса, соответственно. Для объекта, на который указывает выражение, ожидаемый тип которого является анонимным типом доступа к специфическому помеченному типу, объект является динамически помеченным, если выражение, игнорируя окружающие скобки, имеет вид X'Access, где X имеет тип класса, или имеет вид new T'(...), где T обозначает тип класса. В противном случае объект статически или динамически помечен в зависимости от того, является ли тип обозначенного типа выражения специфическим или типовым для класса, соответственно.
Правила легальности
Вызов операции диспетчеризации не должен иметь как динамически, так и статически помеченные управляющие операнды.
Если ожидаемый тип для выражения или имени — это определенный помеченный тип, то выражение или имя не должны быть динамически помечены, если это не управляющий операнд в вызове операции диспетчеризации. Аналогично, если ожидаемый тип выражения — анонимный тип доступа к специфическому помеченному типу, то объект, на который указывает выражение, не должен быть динамически помечен, если это не управляющий операнд в вызове операции диспетчеризации.
В объявлении операции диспетчеризации помеченного типа везде, где подтип помеченного типа появляется как подтип профиля (см. 6.1), он должен статически соответствовать первому подтипу помеченного типа. Если операция диспетчеризации переопределяет унаследованную подпрограмму, она должна быть совместима с подтипом унаследованной подпрограммы. Конвенция унаследованной операции диспетчеризации — это конвенция соответствующей примитивной операции родительского или родоначального типа. По умолчанию конвенция операции диспетчеризации, переопределяющей унаследованную примитивную операцию, — это конвенция унаследованной операции; если операция переопределяет несколько унаследованных операций, то они все должны иметь одинаковую конвенцию. Явно объявленная операция диспетчеризации не может иметь конвенцию Intrinsic.
Выражение по умолчанию default_expression для управляющего формального параметра операции диспетчеризации должно быть неопределённым по тегу.
Если операция диспетчеризации определена с помощью subprogram_renaming_declaration или с помощью инстанцирования обобщённой подпрограммы, любой параметр доступа переименованной подпрограммы или обобщённой подпрограммы, который соответствует управляющему параметру доступа операции диспетчеризации, должен иметь подтип, исключающий null.
Одна и та же подпрограмма не может быть операцией диспетчеризации для двух или более различных помеченных типов.
Явное объявление примитивной подпрограммы помеченного типа должно произойти до того, как тип будет заморожен (см. 13.14). Например, новые операции диспетчеризации не могут быть добавлены после того, как существуют объекты или значения типа, или после получения расширения записи из него, или после тела.
Динамическая семантика
Для выполнения вызова операции диспетчеризации типа T, значение управляющего тега определяет, какое тело подпрограммы выполняется. Значение управляющего тега определяется следующим образом:
- Если один или несколько управляющих операндов статически помечены, то значение управляющего тега определяется статически, как тег T.
- Если один или несколько управляющих операндов динамически помечены, то значение управляющего тега не определяется статически, а определяется тегами управляющих операндов. Если существует более одного динамически помеченного управляющего операнда, проверяется, что у них все одинаковый тег. Если эта проверка не пройдена, Constraint_Error поднимается, за исключением случаев вызова function_call, где имя обозначает объявление оператора равенства (предопределённого или пользователем), который возвращает Boolean, в этом случае результат вызова определён так, чтобы указать на неравенство, и не выполняется subprogram_body. Эта проверка выполняется до оценки любых управляющих операндов, неопределенных по тегу.
- Если все управляющие операнды (если таковые имеются) неопределены по тегу, то:
Если вызов имеет управляющий результат или управляющий результат доступа и сам по себе или обозначает управляющий операнд вложенного вызова операции диспетчеризации потомка типа T, то его значение управляющего тега определяется значением управляющего тега этого вложенного вызова;
Если вызов имеет управляющий результат или управляющий результат доступа и (возможно, в скобках, квалифицированный или дереференцированный) является выражением оператора assignment_statement, целевой которого имеет тип класса, то его значение управляющего тега определяется целью;
В противном случае значение управляющего тега определяется статически, как тег типа T.
При выполнении вызова операции диспетчеризации выполняемое действие определяется свойствами соответствующей операции диспетчеризации конкретного типа, идентифицированного значением управляющего тега:
- если соответствующая операция явно объявлена для этого типа, даже если объявление происходит в частной части, то действие включает вызов явного тела операции;
- если соответствующая операция неявно объявлена для этого типа и реализована в записи или защищённой подпрограмме (см. 9.1 и 9.4), то действие включает вызов этой записи или защищённой подпрограммы, при этом целевой объект задаётся первым фактическим параметром вызова, а фактические параметры записи или защищённой подпрограммы задаются оставшимися фактическими параметрами вызова, если таковые имеются;
- если соответствующая операция является предопределённым оператором, то действие включает вызов этого оператора;
- в противном случае действие такое же, как действие для соответствующей операции родительского типа или предкового типа, из которого операция была унаследована, за исключением того, что могут применяться дополнительные проверки инварианта (см. 7.3.2) и проверки пост-условия для всего класса (см. 6.1.1). Если существует более одной такой соответствующей операции, то действие – это действие для операции, которая не является процедурой null, если таковая имеется; в противном случае действие – это действие произвольной из этих операций.
ПРИМЕЧАНИЕ 1 Тело, которое должно быть выполнено для вызова операции диспетчеризации, определяется меткой; не имеет значения, определяется ли эта метка статически или динамически, и не имеет значения, виден ли объявленная подпрограмма в месте вызова.
ПРИМЕЧАНИЕ 2 Этот подпункт охватывает вызовы диспетчеризованных подпрограмм помеченного типа. Правила для проверки принадлежности к помеченному типу описаны в 4.5.2. Управление определением метки для выражения_присваивания описано в 5.2.
ПРИМЕЧАНИЕ 3 Вызов диспетчеризации может направить на тело, объявление которого не видно в месте вызова.
ПРИМЕЧАНИЕ 4 Вызов через значение доступа к подпрограмме никогда не является вызовом диспетчеризации, даже если значение доступа обозначает операцию диспетчеризации. Аналогично, вызов, чьё префикс обозначает объявление_переименования_подпрограммы, не может быть вызовом диспетчеризации, если переименование само по себе не является объявлением примитивной подпрограммы.