Différences entre la spécialisation des modèles et la surcharge des fonctions?

Donc, je sais qu'il y a une différence entre ces deux morceaux de code:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

template <>
int inc(const int& t)
{
    return t + 1;
}

Et

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 1;
}

Je suis confus quant à ce que les différences fonctionnelles entre ces deux sommes. Quelqu'un peut-il montrer des situations où ces extraits agissent différemment les uns des autres?

21
demandé sur rlbond 2009-10-03 01:39:13

5 réponses

Je ne peux penser qu'à quelques différences - voici quelques exemples qui ne causent pas nécessairement de dommages (je pense). J'Omets des définitions pour le garder laconique

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
template <> int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses explicit specialization

// --- against ---

template <typename T> T inc(const T& t);
namespace G { using ::inc; }
int inc(const int& t);
namespace G { void f() { G::inc(10); } } // uses template

C'est parce que les spécialisations ne sont pas trouvées par recherche de nom, mais par correspondance d'argument, donc une déclaration using considérera automatiquement une spécialisation introduite plus tard.

Ensuite, vous ne pouvez bien sûr pas spécialiser partiellement les modèles de fonction. La surcharge accomplit cependant quelque chose de très similaire par partielle commander (en utilisant différents types maintenant, pour faire mon point)

template <typename T> void f(T t); // called for non-pointers
template <typename T> void f(T *t); // called for pointers.

int a;
void e() {
  f(a); // calls the non-pointer version
  f(&a); // calls the pointer version
}

Cela ne serait pas possible avec la spécialisation explicite du modèle de fonction. Un autre exemple est lorsque des références sont impliquées, ce qui provoque la déduction des arguments du modèle pour rechercher une correspondance exacte des types impliqués (relations de base/classe dérivée modulo et constance):

template<typename T> void f(T const &);
template<> void f(int * const &);

template<typename T> void g(T const &);
void g(int * const &);

int a[5];
void e() {
  // calls the primary template, not the explicit specialization
  // because `T` is `int[5]`, not `int *`
  f(a);

  // calls the function, not the template, because the function is an
  // exact match too (pointer conversion isn't costly enough), and it's 
  // preferred. 
  g(a);
}

Je vous recommande de toujours utiliser la surcharge, car elle est plus riche (permet quelque chose comme la spécialisation partielle permettrait), et dans addition vous pouvez placer la fonction dans n'importe quel espace de noms que vous voulez (bien qu'il ne soit plus strictement surchargé). Par exemple, au lieu d'avoir à spécialiser std::swap dans l'espace de noms std::, Vous pouvez placer votre surcharge swap dans votre propre espace de noms et le rendre appelable par ADL.

Quoi que vous fassiez, Ne mélangez jamais spécialisation et surcharge , ce sera un enfer d'un gâchis commeCet article souligne. La norme a un beau paragraphe à ce sujet

Le placement de déclarations de spécialisation explicites pour les modèles de fonctions, les modèles de classes, les fonctions membres des modèles de classes, les membres de données statiques des modèles de classes, les classes membres des modèles de classes, les modèles de classes membres des modèles de classes, les modèles de fonctions membres des modèles de classes, les fonctions membres et la placement de déclarations de spécialisation partielle de modèles de classe, modèles de classe de membre de classes non-MODÈLE, modèles de classe de membre de modèles de classe, etc., peut affecter si un programme est bien formé en fonction du positionnement relatif des déclarations de spécialisation explicites et de leurs points d'instanciation dans l'Unité de traduction comme spécifié ci-dessus et ci-dessous. Lorsque vous écrivez une spécialisation, faites attention à son emplacement; ou pour le faire compiler sera un tel essai pour allumer son auto-immolation.

14
répondu Johannes Schaub - litb 2013-03-19 13:42:30

La spécialisation des modèles est plus générique que la simple surcharge. Vous pouvez spécialiser des choses comme des classes plutôt que de simples fonctions. La surcharge s'applique uniquement aux fonctions.

UPDATE: pour clarifier plus par le commentaire D'AraK, vous comparez vraiment les pommes et les oranges ici. La surcharge de fonction est utilisée pour introduire la possibilité d'avoir différentes fonctions partagent un seul nom, si elles ont des signatures différentes. La spécialisation de modèle est utilisée pour définir un code spécifique extrait pour un paramètre de type spécifique. Vous ne pouvez pas avoir de spécialisation de modèle si vous n'avez pas de modèle. Si vous supprimez le premier morceau de code qui déclare le modèle générique, vous recevrez une erreur de compilation si vous essayez d'utiliser la spécialisation de modèle.

Ainsi, l'objectif de la spécialisation de modèle est assez différent d'une surcharge de fonction. Ils se comportent de la même manière dans votre exemple alors qu'ils sont fondamentalement différents.

Si vous fournissez une surcharge, vous déclarent une méthode indépendante qui a le même nom. Vous n'empêchez pas l'utilisation du modèle avec le paramètre type spécifique. Pour démontrer ce fait, essayez:

template <typename T>
T inc(const T& t)
{
    return t + 1;
}

int inc(const int& t)
{
    return t + 42;
}

#include <iostream>
int main() {
   int x = 0;
   x = inc<int>(x);
   std::cout << "Template: " << x << std::endl; // prints 1.

   x = 0;
   x = inc(x);
   std::cout << "Overload: " << x << std::endl; // prints 42.
}

Comme vous pouvez le voir, dans cet exemple, il existe deux fonctions inc distinctes pour les valeurs int: inc(const int&) et inc<int>(const int&). Vous ne pouvez pas développer le modèle générique en utilisant int Si vous avez utilisé la spécialisation de modèle.

5
répondu Mehrdad Afshari 2009-10-02 22:10:45

Un tel exemple:

#include <cstdio>

template <class T>
void foo(T )
{
    puts("T");
}

//template <>
void foo(int*)
{
    puts("int*");
}

template <class T>
void foo(T*)
{
    puts("T*");
}

int main()
{
    int* a;
    foo(a);
}

Il est en fait suggéré d'utiliser des surcharges non-template pour les fonctions et de laisser la spécialisation pour les classes. Il est discuté plus longuement dans Pourquoi ne pas spécialiser les Modèles de fonction?

4
répondu UncleBens 2009-10-02 22:02:27

AFAIK il n'y a pas de différence fonctionnelle. Tout ce que je peux ajouter, c'est que si vous avez à la fois une spécialisation de fonction de modèle et une fonction ordinaire définie, il n'y a pas d'ambiguïté de surcharge car la fonction ordinaire est favorisée.

1
répondu Troubadour 2009-10-02 22:01:00

Juste pour développer le premier point mentionné par litb dans sa réponse . Les spécialisations ne sont vérifiées qu'une fois que la résolution de surcharge a sélectionné un modèle principal. Le résultat peut conduire à des surprises où une fonction est surchargée et a des spécialisations explicites:

template <typename T> void foo (T);  // Primary #1
template <> void foo<int*> (int*);   // Specialization of #1

template <typename T> void foo (T*); // Primary #2

void bar (int * i)
{
  foo(i);
}

Lors du choix de la fonction à appeler, les étapes suivantes ont lieu:

  1. Name lookup trouve les deux modèles principaux.
  2. chaque modèle est spécialisé et surchargé la résolution tente de sélectionner une meilleure fonction en fonction des conversions entre les arguments et les paramètres.
  3. dans ce cas, il n'y a pas de différence dans la qualité des conversions.
  4. les règles de classement partiel sont ensuite utilisées pour sélectionner le modèle le plus spécialisé. Dans ce cas, c'est le deuxième parimaire " foo (T*)".

Ce n'est qu'après ces étapes, lorsque la meilleure fonction a été sélectionnée, que les spécialisations explicites de la fonction sélectionnée seront prises en compte. (Dans ce cas, le primaire # 2 n'en a pas, donc aucun n'est considéré).

La seule façon d'appeler la spécialisation explicite ci-dessus ici, est d'utiliser réellement des arguments de modèle explicites dans l'appel:

void bar (int * i)
{
  foo<int*> (i);  // expliit template argument forces use of primary #1
}

Une bonne règle de base est d'essayer d'éviter d'avoir des surcharges qui sont aussi explicitement spécialisées, car les règles résultantes sont assez complexes.

1
répondu Richard Corden 2017-05-23 12:07:13