9/9

9. Templates

Rem. Cette partie est donnée à titre informatif uniquement.

Les templates correspondent à une généricité dite statique en C++ , c’est à dire des fonctions dont le type et l’implémentation sont entièrement connus au moment de la compilation. L’avantage d’une généricité statique par rapport à la généricité dynamique (adaptation de types en cours d’exécution) correspond à son efficacité: aucun temps de calcul supplémentaire par rapport à une implémentation spécialisée.

L’usage commun des templates consiste à créer des fonctions (ou des objets) pouvant s’appliquer (ou contenir) des types arbitraires. Par exemple les conteneurs template de la STL peuvent contenir n’importe quel type, mais celui-ci doit être connu au moment de la compilation.

Functions templates

Supposons que l’on souhaite créer une fonction pouvant afficher tous les éléments d’un vecteur de n’importe quel type (ex. std::array, ou std::vector) possédant une taille arbitraire.

La signature d’une telle fonction serait, pour un type donné, void display_all_elements( const someType& container ). Il est possible d’indiquer au compilateur que le type someType est un type générique template, en indiquant la directive template <typename someType>.

L’implémentation complète est la suivante

#include <iostream>
#include <vector>
#include <array>


// Generic implementation of function displaying all elements of a vector-type
template <typename someType>
void display_all_elements( const someType& container )
{
    // We suppose we can access elements using container[k] syntax
    for(size_t k=0; k<container.size(); ++k)
        std::cout << container[k] <<" ";
    std::cout << std::endl;
}

int main()
{
    // Declaration of two different types
    std::array<float,2> v1 {1.4f, 2.5f};
    std::vector<int>    v2 {7, 8, 12, -1, 6, 3};

    // Use of the same function
    display_all_elements( v1 );
    display_all_elements( v2 );

    return 0;
}
  • someType peut être remplacé par n’importe quel nom. Il est commun d’utiliser T.

  • L’implémentation du corps de la fonction est similaire à n’importe quelle autre implémentation que pour un type non template.

  • Les fonctions avec arguments templates peuvent être appelées comme n’importe quelle autre fonction. Le type passé en argument au moment de l’appel à la fonction indique au compilateur quel type correspond au template.

Variante de la fonction précédente. Dans le cas où l’on souhaite rendre la fonction encore plus générique, il est possible d’utiliser une boucle de type 'range-for' à la place de supposer que les éléments du conteneur sont indexables sous la forme container[k]. La fonction suivante peut alors être appliquée à tous les conteneurs de la STL dont les éléments peuvent être affichés (ex. list, set, etc.)

template <typename someType>
void display_all_elements( const someType& container )
{
    // Valid for any iterator based container
    for( const auto& element : container )
        std::cout << element <<" ";
    std::cout << std::endl;
}

Objets templates

Les objets peuvent également utiliser des templates. Supposons que l’on souhaite définir un type vec3 générique dont les composantes (x,y,z) puissent être de n’importe quel type (ex. int, float, double, long double, etc.)

#include <iostream>
#include <vector>
#include <array>


// Generic vec3 containing arbitrary type
template <typename T>
struct vec3
{
    T x;
    T y;
    T z;
};


int main()
{
    // Declaring vec3 as containing various fundamental types
    vec3<float> p1 {1.0f, 2.5f, 3.5f};
    vec3<int> p2 {5, 4, 3};
    vec3<double> p3 {5.7, 2.6, 1.1};

    // Template are generic, each element of vec can be any type, for instance std::vector
    vec3< std::vector<int> > p { {1,2,3,5}, {7,2}, {4,1,-2}};
    p.y = {8,5,4,1};
    std::cout<< p.y[2] << std::endl;

    return 0;
}

Rem. Notez que les objets templates doivent explicitement indiquer le type entre chevrons au moment de leur déclaration [1].

Notes générales sur les templates

  • Les templates sont un mécanisme C++ puissant permettant de gérer des types génériques, et même de réaliser des calculs au moment de la compilation. Cependant la syntaxe associée au template peut rapidement devenir complexe. Ne sur-utilisez pas les templates si cela n’est pas nécessaire.

  • Le compilateur instancie un code template à chaque fois qu’il rencontre un appel à un appel template. Cela peut aboutir à un temps de compilation particulièrement élevé si le code utilise de nombreux templates.

  • Les templates ne suivent pas le principe de compilation séparée: l’ensemble du code doit être accessible à chaque fois qu’un appel template est rencontrée à la compilation. L’ensemble du code template doit ainsi être présent dans les fichiers d’en-tête (ou du moins, entièrement accessible dans le fichier dans lequel il est utilisé).


1. En C++17 cette contrainte est levée à partir du moment où le compilateur peut déduire le type de manière non ambigu lors de la déclaration