Point d'instanciation
les modèles seront instanciés lorsqu'ils seront utilisés.
Pas exactement, mais en gros. Le point précis d'instanciation est un peu subtil, et je vous délègue à la section nommée Point d'instanciation dans le beau livre de Vandevoorde/Josuttis.
Cependant, les compilateurs n'implémentent pas nécessairement les POIs correctement : Bogue c++/41995 : Point d'instanciation incorrect pour le modÃ?le de fonction.
Instanciation partielle
les modèles seront instanciés lorsqu'ils seront utilisés.
C'est partiellement correct. C'est vrai pour les modèles de fonction, mais pour les modèles de classe, seules les fonctions membres qui sont utilisées sont instanciées. Le code suivant est bien formé :
#include <iostream>
template <typename> struct Foo {
void let_me_stay() {
this->is->valid->code. get->off->my->lawn;
}
void fun() { std::cout << "fun()" << std::endl; }
};
int main () {
Foo<void> foo;
foo.fun();
}
let_me_stay()
est vérifié syntaxiquement (et la syntaxe y est correcte), mais pas sémantiquement (c'est-à-dire qu'il n'est pas interprété).
Recherche biphasée
Cependant, seuls code dépendant est interprété plus tard ; clairement, dans Foo<>
, this
dépend de l'identifiant exact du modèle avec lequel l'application Foo<>
est instancié, nous avons donc reporté le contrôle d'erreur de l'option Foo<>::let_me_alone()
jusqu'au moment de l'instanciation.
Mais si nous n'utilisons pas quelque chose qui dépend de l'instanciation spécifique, le code doit être bon. Par conséquent, ce qui suit est no bien formé :
$ cat non-dependent.cc
template <typename> struct Foo {
void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; }
};
int main () {} // note: no single instantiation
Mine
est un symbole totalement inconnu du compilateur, contrairement à this
pour lesquels le compilateur peut déterminer la dépendance d'instance.
Le point clé ici est que C++ utilise un modèle de lookup biphasé Il s'agit d'un concept souvent mal compris ou inconnu, de nombreux programmeurs C++ supposent que les modèles ne sont pas analysés du tout avant l'instanciation, mais ce n'est qu'un mythe provenant de Microsoft C++).
Instanciation complète des modèles de classe
La définition de Foo<>::let_me_stay()
a fonctionné parce que la vérification des erreurs a été reportée à plus tard, comme pour les this
le pointeur, qui est dépendant. Sauf dans le cas où vous auriez fait usage de
instanciations explicites
cat > foo.cc
#include <iostream>
template <typename> struct Foo {
void let_me_stay() { this->is->valid->code. get->off->my->lawn; }
void fun() { std::cout << "fun()" << std::endl; }
};
template struct Foo<void>;
int main () {
Foo<void> foo;
foo.fun();
}
g++ foo.cc
error: error: ‘struct Foo<void>’ has no member named ‘is’
Définitions de modèles dans différentes unités de traduction
Lorsque vous instanciez explicitement, vous instanciez explicitement. Et faire tous visibles par l'éditeur de liens, ce qui signifie également que la définition du modèle peut résider dans différentes unités de traduction :
$ cat A.cc
template <typename> struct Foo {
void fun(); // Note: no definition
};
int main () {
Foo<void>().fun();
}
$ cat B.cc
#include <iostream>
template <typename> struct Foo {
void fun();
};
template <typename T>
void Foo<T>::fun() {
std::cout << "fun!" << std::endl;
} // Note: definition with extern linkage
template struct Foo<void>; // explicit instantiation upon void
$ g++ A.cc B.cc
$ ./a.out
fun!
Cependant, vous devez explicitement instancier pour que tous les arguments du modèle soient utilisés, autrement
$ cat A.cc
template <typename> struct Foo {
void fun(); // Note: no definition
};
int main () {
Foo<float>().fun();
}
$ g++ A.cc B.cc
undefined reference to `Foo<float>::fun()'
Petite remarque sur le lookup biphasé : La norme ne dicte pas si un compilateur implémente effectivement la recherche biphasée. Pour être conforme, cependant, il devrait fonctionner comme si c'était le cas (tout comme l'addition ou la multiplication ne doivent pas nécessairement être effectuées en utilisant des instructions CPU d'addition ou de multiplication.