Je lisais sur les fonctions de modèle et je suis devenu confus par ce problème:
#include <iostream>
void f(int) {
std::cout << "f(int)\n";
}
template<typename T>
void g(T val) {
std::cout << typeid(val).name() << " ";
f(val);
}
void f(double) {
std::cout << "f(double)\n";
}
template void g<double>(double);
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // d f(int), this is surprising
g(1); // i f(int)
}
Les résultats sont les mêmes si je n'écris pas template void g<double>(double);
.
Je pense g<double>
à instancier après f(double)
, et donc l'appel à f
en g
devrait appeler f(double)
. Étonnamment, il appelle encore f(int)
à g<double>
. Quelqu'un peut-il m'aider à comprendre cela?
Après avoir lu les réponses, j'ai compris ce qu'est vraiment ma confusion.
Voici un exemple mis à jour. Il est pratiquement inchangé sauf que j'ai ajouté une spécialisation pour g<double>
:
#include <iostream>
void f(int){cout << "f(int)" << endl;}
template<typename T>
void g(T val)
{
cout << typeid(val).name() << " ";
f(val);
}
void f(double){cout << "f(double)" << endl;}
//Now use user specialization to replace
//template void g<double>(double);
template<>
void g<double>(double val)
{
cout << typeid(val).name() << " ";
f(val);
}
int main() {
f(1.0); // f(double)
f(1); // f(int)
g(1.0); // now d f(double)
g(1); // i f(int)
}
Avec la spécialisation utilisateur, g(1.0)
se comporte comme je m'y attendais.
Le compilateur ne doit-il pas automatiquement effectuer cette même instanciation g<double>
au même endroit (ou même après main()
, comme décrit dans la section 26.3.3 du langage de programmation C ++ , 4e édition)?
la source
g(1)
,,i f(int)
me donne . Vous avez écritd f(double)
. C'était une faute de frappe?Réponses:
Le nom
f
est un nom dépendant (cela dépendT
via l'argumentval
) et il sera résolu en deux étapes :void f(double)
n'est pas visible dans le contexte de définition de modèle et ADL ne le trouvera pas non plus, carNous pouvons légèrement modifier votre exemple:
Maintenant, ADL trouvera
void f(Double)
dans la deuxième étape, et la sortie sera6Double f(Double)
. Nous pouvons désactiver ADL en écrivant(f)(val)
(ou::f(val)
) au lieu def(val)
. Ensuite, la sortie sera6Double f(Int)
, en accord avec votre exemple.la source
void f(double)
n'est pas visible - ce contexte se termine avant sa déclaration. À l'étape 2, ADL ne trouvera rien, donc le contexte d' instanciation du modèle ne joue aucun rôle ici.void f(double)
, donc cette fonction est visible d'elle. Cef
n'est pas un nom dépendant. S'il y avait une meilleure correspondance pourf(val);
déclaré après la définition deg<double>
, elle ne sera pas trouvée non plus. La seule façon de «regarder vers l'avenir» est ADL (ou un ancien compilateur qui n'implémente pas correctement la recherche en deux phases).main()
. Ils ne verront pasf(double)
, car lorsque l'instanciation se produit, il est trop tard: la première phase de la recherche a déjà été effectuée et n'a trouvé aucun résultatf(double)
.Le problème
f(double)
n'a pas été déclaré au point où vous l'appelez; si vous déplacez sa déclaration devant letemplate g
, il sera appelé.Edit: Pourquoi utiliser l'instanciation manuelle?
(Je ne parlerai que des modèles de fonction, l'argumentation analogue s'applique également aux modèles de classe.) L'utilisation principale est de réduire les temps de compilation et / ou de cacher le code du modèle aux utilisateurs.
Les programmes C ++ sont intégrés aux binaires en 2 étapes: compilation et liaison. Pour que la compilation d'un appel de fonction réussisse, seul l'en-tête de la fonction est nécessaire. Pour que la liaison réussisse, un fichier objet contenant le corps compilé de la fonction est nécessaire.
Désormais, lorsque le compilateur voit l'appel d'une fonction basée sur un modèle , ce qu'il fait dépend de s'il connaît le corps du modèle ou uniquement l'en-tête. S'il ne voit que l'en-tête, il fait la même chose que si la fonction n'était pas basée sur un modèle: place les informations sur l'appel de l'éditeur de liens dans le fichier objet. Mais s'il voit également le corps du modèle, il fait aussi autre chose: il instancie l'instance appropriée du corps, compile ce corps et le place également dans le fichier objet.
Si plusieurs fichiers source appellent la même instance de la fonction de modèle, chacun de leurs fichiers objet contiendra une version compilée de l'instance de la fonction. (Linker le sait et résout tous les appels à une seule fonction compilée, donc il n'y en aura qu'un dans le binaire final du programme / bibliothèque.) Cependant, pour compiler chacun des fichiers source, la fonction devait être instanciée et compilé, ce qui a pris du temps.
Il suffit que l'éditeur de liens fasse son travail si le corps de la fonction se trouve dans un seul fichier objet. Instancier manuellement le modèle dans un fichier source est un moyen de faire en sorte que le compilateur place le corps de la fonction dans le fichier objet du fichier source en question. (C'est un peu comme si la fonction était appelée, mais l'instanciation est écrite dans un endroit où l'appel de fonction serait invalide.) Lorsque cela est fait, tous les fichiers qui appellent votre fonction peuvent être compilés en ne connaissant que l'en-tête de la fonction, donc gain de temps qu'il faudrait pour instancier et compiler le corps de la fonction avec chacun des appels.
La deuxième raison (la dissimulation de l'implémentation) pourrait avoir un sens maintenant. Si un auteur de bibliothèque souhaite que les utilisateurs de sa fonction de modèle puissent utiliser la fonction, il leur donne généralement le code du modèle, afin qu'ils puissent le compiler eux-mêmes. Si elle voulait garder secret le code source du modèle, elle pourrait instancier manuellement le modèle dans le code qu'elle utilise pour construire la bibliothèque et donner aux utilisateurs la version d'objet ainsi obtenue au lieu de la source.
Est-ce que cela a un sens?
la source
template void g<double>(double);
soi-disant instanciation manuelle (notez letemplate
sans parenthèses angulaires, c'est une caractéristique distinctive de la syntaxe); qui indique au compilateur de créer une instance de la méthode. Ici, cela a peu d'effet, s'il n'était pas là, le compilateur générerait l'instance à l'endroit où l'instance est appelée. L'instanciation manuelle est une fonctionnalité rarement utilisée, je dirai pourquoi vous voudrez peut-être l'utiliser après avoir confirmé que la chose est maintenant plus claire :-)