Что такое мономорфизация с контекстом на С++?

Недавний разговор Дейва Германа в Rust сказал, что они заимствовали это свойство у С++. Я не мог найти ничего вокруг темы. Может ли кто-нибудь объяснить, что такое мономорфизация?

Ответ 1

Мономорфизация означает создание специализированных версий обобщенных функций. Если я напишу функцию, которая извлекает первый элемент любой пары:

fn first<A, B>(pair: (A, B)) -> A {
    let (a, b) = pair;
    return a;
}

и затем я вызываю эту функцию дважды:

first((1, 2));
first(("a", "b"));

Компилятор сгенерирует две версии first(), одну для пар целых чисел и одну для пар строк.

Название происходит от термина языка полиморфизма, означающего одну функцию, которая может работать со многими типами данных. Мономорфизация - это преобразование из полиморфного в мономорфный код.

Ответ 2

Не уверен в этом; вы могли бы сослаться на разговор? Возможно, это было небрежное замечание.

Герман мог бы придумать термин для чего-то вроде специализации шаблонов, который генерирует типы/объекты, которые являются взаимно несвязанными (не полиморфными или "мономорфными" ) из шаблона, который является полиморфной структурой.

Ответ 3

Не уверен, что кто-то еще смотрит на это, но документация Rust на самом деле упоминает, как в этом процессе не достигается абстракция затрат. Из выполнения кода с использованием обобщений:

Вы можете задаться вопросом, есть ли затраты времени выполнения при использовании параметров универсального типа. Хорошей новостью является то, что Rust реализует дженерики таким образом, что ваш код не работает с медленными типами медленнее, чем с конкретными типами.

Rust выполняет это, выполняя мономорфизацию кода, который использует дженерики во время компиляции. Мономорфизация - это процесс превращения универсального кода в конкретный код путем заполнения конкретных типов, которые используются при компиляции.

В этом процессе компилятор делает противоположные шаги, которые мы использовали для создания универсальной функции в листинге 10-5: компилятор просматривает все места, где вызывается универсальный код, и генерирует код для конкретных типов, с которыми вызывается универсальный код,

Давайте посмотрим, как это работает, на примере, который использует стандартные перечисления Option библиотек:

let integer = Some(5);
let float = Some(5.0);

Когда Rust компилирует этот код, он выполняет мономорфизацию. Во время этого процесса компилятор считывает значения, которые использовались в экземплярах Option, и определяет два вида Option: один - i32, а другой - f64. Таким образом, он расширяет общее определение Option в Option_i32 и Option_f64, тем самым заменяя общее определение на конкретные.

Мономорфизированная версия кода выглядит следующим образом. Универсальный Option заменяется конкретными определениями, созданными компилятором:

// Filename: src/main.rs

enum Option_i32 {
    Some(i32),
    None,
}

enum Option_f64 {
    Some(f64),
    None,
}

fn main() {
    let integer = Option_i32::Some(5);
    let float = Option_f64::Some(5.0);
}

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