Pourquoi la déclaration et l'implémentation des modèles ne peuvent-elles pas être séparées dans un fichier .h et un fichier .cpp ?

Lorsqu’un modèle est utilisé, le compilateur doit connaître sa définition exacte pour générer le code spécifique correspondant à chaque type passé en paramètre. Or, si l’implémentation est placée dans un fichier .cpp distinct, le compilateur ne peut pas accéder à cette définition au moment de l’instanciation — même si la déclaration est visible. Cela entraîne des erreurs de liaison (linker errors), car le lien ne trouve pas de symbole correspondant à l’implémentation générée.

Par exemple, considérons ce modèle :

template <typename U>
class Container {
public:
    U& Get();
    void Set(const U& value);
private:
    U data;
};

Si l’on place les définitions des méthodes membres dans un fichier .cpp :

// container.cpp
template <typename U>
U& Container<U>::Get() {
    return data;
}

template <typename U>
void Container<U>::Set(const U& value) {
    data = value;
}

Alors, lors de la compilation d’un autre fichier utilisant Container<int>, le compilateur ne verra pas ces définitions. Il crée donc un symbole externe, mais le lien échoue car aucune version instanciée n’a été produite dans le fichier .cpp.

La solution standard consiste donc à placer toutes les définitions de modèles directement dans le fichier d’en-tête. C’est pourquoi les bibliothèques comme STL incluent toujours les implémentations dans les fichiers .h.

Une alternative théorique existait via le mot-clé export, permettant de déclarer un modèle dans un .h et son implémentation dans un .cpp avec export template. Par exemple :

// output.h
template <typename T> void print(const T& value);

// output.cpp
#include <iostream>
export template <typename T> void print(const T& value) {
    std::cout << value << std::endl;
}

// main.cpp
#include "output.h"
int main() {
    print(42);
    print("Bonjour");
    return 0;
}

Cependant, export a été abandonné par la norme C++11. La plupart des compilateurs majeurs — notamment GCC et Visual Studio — ne le supportent pas, rendant cette approche inutilisable en pratique. Même les outils comme Comeau C/C++ ou Intel 7.x ont fini par abandonner son utilisation en raison de performances médiocres et de complexité de mise en œuvre.

Le cœur du problème réside dans le fait que les modèles sont instanciés à chaque utilisation avec un type concret. Le compilateur doit donc disposer de la totalité de la définition au moment de la compilation. Ce comportement est incompatible avec le modèle traditionnel de compilation séparée, où les définitions sont regroupées dans des unités de traduction distinctes et reliées ensuite par le lien.

Ainsi, bien que cela contrecarre l’idéal de séparation claire entre interface et implémentation, la pratique courante reste d’implémenter les modèles entièrement dans les fichiers d’en-tête.

Étiquettes: template C++ compilation linking header-only

Publié le 18 octobre à 02h12