web-dev-qa-db-fra.com

Qu'est-ce que la monomorphisation avec contexte en C ++?

récent discours de Dave Herman in Rust a dit qu'ils avaient emprunté cette propriété à C++. Je n'ai rien trouvé autour du sujet. Quelqu'un peut-il expliquer ce que signifie la monomorphisation?

61
unj2

La monomorphisation signifie générer des versions spécialisées de fonctions génériques. Si j'écris une fonction qui extrait le premier élément d'une paire:

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

puis j'appelle cette fonction deux fois:

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

Le compilateur générera deux versions de first(), une spécialisée pour les paires d'entiers et l'autre spécialisée pour les paires de chaînes.

Le nom dérive du terme de langage de programmation "polymorphisme" - ce qui signifie une fonction qui peut traiter de nombreux types de données. La monomorphisation est la conversion du code polymorphe en code monomorphe.

98
Niko Matsakis

Je ne sais pas si quelqu'un regarde toujours cela, mais la documentation Rust mentionne en fait comment il n'obtient aucune abstraction des coûts grâce à ce processus. De ( Performances du code à l'aide de génériques :

Vous vous demandez peut-être s'il y a un coût d'exécution lorsque vous utilisez des paramètres de type génériques. La bonne nouvelle est que Rust implémente les génériques de telle manière que votre code ne s'exécute pas plus lentement en utilisant des types génériques qu'avec des types concrets.

Rust y parvient en effectuant une monomorphisation du code qui utilise des génériques au moment de la compilation. La monomorphisation est le processus de transformation du code générique en code spécifique en remplissant les types concrets utilisés lors de la compilation.

Dans ce processus, le compilateur fait l'opposé des étapes que nous avons utilisées pour créer la fonction générique dans l'extrait 10-5: le compilateur examine tous les endroits où le code générique est appelé et génère du code pour les types concrets avec lesquels le code générique est appelé. .

Voyons comment cela fonctionne avec un exemple qui utilise l'énumération Option de la bibliothèque standard:

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

Lorsque Rust compile ce code, il effectue la monomorphisation. Au cours de ce processus, le compilateur lit les valeurs qui ont été utilisées dans les instances d'Option et identifie deux types d'Option: l'un est i32 et l'autre est f64 En tant que tel, il étend la définition générique d'Option en Option_i32 et Option_f64, remplaçant ainsi la définition générique par des définitions spécifiques.

La version monomorphisée du code ressemble à ce qui suit. L'option générique est remplacée par les définitions spécifiques créées par le compilateur:

// 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);
}

Parce que Rust compile du code générique en code qui spécifie le type dans chaque instance, nous ne payons aucun coût d'exécution pour l'utilisation de génériques. Lorsque le code s'exécute, il fonctionne exactement comme si nous avions dupliqué chaque définition Le processus de monomorphisation rend les génériques de Rust extrêmement efficaces à l'exécution.

5
Micheal

Je n'en suis pas sûr; pourriez-vous faire un lien avec la conférence? C'était peut-être une remarque désinvolte.

Herman pourrait avoir inventé un terme pour quelque chose comme la spécialisation du modèle, qui génère des types/objets qui sont mutuellement indépendants (non polymorphes ou "monomorphes") du modèle, qui est une structure polymorphe.

5
Potatoswatter

Il y a une belle explication de la monomorphisation dans le Rust Book

La monomorphisation est le processus de transformation du code générique en code spécifique en remplissant les types concrets utilisés lors de la compilation.

Dans l'exemple du livre, si vous avez défini des variables avec Some :

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

Lorsque Rust compile ce code, il effectue la monomorphisation. Au cours de ce processus, le compilateur lit les valeurs qui ont été utilisées dans Option<T> instances et identifie deux types de Option<T>: l'un est i32 et l'autre est f64. En tant que tel, il élargit la définition générique de Option<T> en Option_i32 et Option_f64, remplaçant ainsi la définition générique par des définitions spécifiques.

La version monomorphisée du code ressemble à ce qui suit. Le générique Option<T> est remplacé par les définitions spécifiques créées par le compilateur:

Nom de fichier: 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);
}
0
wolendranh