Стирание типа (type erasure) — идиома программирования, один из способов корректно и единообразно обрабатывать данные разного типа. А именно: переменная конкретного типа начинает храниться в ячейке памяти общего типа, без всяких меток типа, и обработка проводится в общем типе, если это можно. На более высоком уровне вычисление планируется так, что преобразование типов «вниз», из общего в конкретный, всегда оказывается корректным.

Некоторые источники считают, что виртуальный полиморфизм — это уже стирание типа.

Ключевой недостаток стирания типов — нельзя надёжно, одним просмотром памяти, узнать при выполнении, какой тип был стёрт. Преимущество стирания типов в компактности.

Примеры Большинство машинных кодов Как правило, команды процессора имеют очень бедный набор доступных типов: целый байт (2, 4, 8, 16 байтов; какой-то из этих целых типов заодно и указатель), дробные 4 и 8 байтов, иногда (SIMD или 3D) небольшие векторы и матрицы из этих типов.

Поверх всего этого языки высокого уровня выстраивают сложную систему типов, и компилятор сам планирует вычисление так, что если, например, загружаются данные из 4-байтовой дробной переменной, то обязательно будут вызываться машинные команды, применимые к 4-байтовому дробному.

Обобщённое программирование в Java Шаблонное программирование в Java работает так. Когда пишут class Wrapper<T> { T value; } создаётся всего один скомпилированный класс (Wrapper.class), и в нём переменная value типа Object. Но компилятор своими силами налаживает работу так, чтобы Wrapper<Integer> не пересекался с Wrapper<String>, и преобразования типов получаются верными.

Именно поэтому запрещается шаблонно наследоваться от Throwable, прямо или косвенно — ведь непонятно, какой из блоков вызвать в этом коде: try { throw new GenericException<Integer>(); } catch (GenericException<Integer> e) { System.err.println("Integer"); } catch (GenericException<String> e) { System.err.println("String"); }

Другой недопустимый код в Java, именно из-за стирания типа — вызов неизвестного заранее конструктора. <T> T instantiateElementType(List<T> arg) { return new T(); // Ошибка }

В Си++ шаблонное программирование не стирает тип, а генерирует для каждой специализации свой машинный код, и аналогичная программа вполне возможна. template <class T> auto instantiateElementType(std::span<const T> arg) { return std::make_unique<T>(); }

Создание виртуального полиморфизма там, где его не было… …Или добавление второй, чисто серверной, таблицы виртуальных методов в шаблон «команда».

Предположим, что есть разные команды — например, «напечатать число» и «напечатать строку». Первая таблица виртуальных методов общая для клиента и сервера, и отвечает за «лёгкие» вещи вроде сериализации и записи в журнал. Только сервер умеет исполнять команды, и для этого нужна отдельная вторая таблица виртуальных методов.

Здесь стирание типа — это хранение команды в , а не в . Таблица виртуальных методов может быть и первая — для единообразной обработки совершенно не связанных типов; так работает тип Си++17.

#include <iostream> #include <memory> #include <vector>

/// Общее для клиента и сервера

class ClientCommand { public: virtual ~ClientCommand() = default; // функции write(), log() и прочие, нам сейчас не нужные

class PrintInt : public ClientCommand { public: int value; PrintInt(int v) : value(v) {}

class PrintString : public ClientCommand { public: std::string value; PrintString(std::string v) : value(std::move(v)) {}

/// Сервер

// Исполнение команд редко бывает шаблонным — сделаем нешаблонно… void doExec(const PrintInt& x) { std::cout << x.value << std::endl; } void doExec(const PrintString& x) { std::cout << x.value << std::endl; }

class ServerCommand { public: void exec() const { value->exec(); }

template <class Clicmd, class... Args> static ServerCommand make(Args&&... args) { return ServerCommand(new Model<Clicmd>(std::forward<Args>(args)...)); } private: class Concept { public: virtual ~Concept() = default; virtual void exec() const = 0;

template <class Clicmd> class Model : public Concept { public: static_assert(std::is_base_of_v<ClientCommand, Clicmd>); Clicmd value; template <class... Args> Model(Args&&... args) : value(std::forward<Args>(args)...) {} void exec() const override { doExec(value); }

std::unique_ptr<Concept> value; ServerCommand(Concept* v) : value(v) {}

int main() { std::vector<ServerCommand> commands; commands.push_back(ServerCommand::make<PrintInt>(42)); commands.push_back(ServerCommand::make<PrintString>("Wikipedia"));

for (auto& cmd : commands) { cmd.exec();

Обработчики событий Embarcadero Delphi и Qt Переменная «кто послал событие» обычно имеет самый общий тип. Например, для кнопки задаём такое событие: procedure MyButtonClick(Sender : TObject); begin with TButton(Sender) do ...; end;

По канонам Delphi параметр Sender имеет самый общий тип TObject. Но мы знаем, что событие применено к кнопке, и соответственно преобразуем тип. Аналогичным образом работает .

Примечания

Категория:Теория типов