Spécialisation partielle du modèle de fonction C ++?


87

Je sais que le code ci-dessous est une spécialisation partielle d'une classe:

template <typename T1, typename T2> 
class MyClass { 
  … 
}; 


// partial specialization: both template parameters have same type 
template <typename T> 
class MyClass<T,T> { 
  … 
}; 

Je sais également que C ++ n'autorise pas la spécialisation partielle du modèle de fonction (seule la totalité est autorisée). Mais mon code signifie-t-il que j'ai partiellement spécialisé mon modèle de fonction pour un / même type d'arguments? Parce que cela fonctionne pour Microsoft Visual Studio 2010 Express! Si non, pouvez-vous expliquer le concept de spécialisation partielle?

#include <iostream>
using std::cin;
using std::cout;
using std::endl;

template <typename T1, typename T2> 
inline T1 max (T1 const& a, T2 const& b) 
{ 
    return a < b ? b : a; 
} 

template <typename T> 
inline T const& max (T const& a, T const& b)
{
    return 10;
}


int main ()
{
    cout << max(4,4.2) << endl;
    cout << max(5,5) << endl;
    int z;
    cin>>z;
}

Recherchez cette analogie de la spécialisation de classe. Si cela s'appelle la spécialisation de classe, alors pourquoi devrais-je considérer la même chose pour la fonction que la surcharge?
Narek

1
Non, la syntaxe de spécialisation est différente. Regardez la syntaxe de spécialisation de fonction (supposée) dans ma réponse ci-dessous.
iammilind

2
Pourquoi cela ne jette-t-il pas une erreur "L'appel au max est ambigu"? Comment se max(5,5)résout max(T const&, T const&) [with T=int]et non max(T1 const&, T2 const&) [with T1=int and T2=int]?
NHDaly

Réponses:


81

La spécialisation partielle des fonctions n'est pas encore autorisée selon la norme. Dans l'exemple, vous surchargez et ne spécialisez pas la max<T1,T2>fonction.
Sa syntaxe aurait dû ressembler un peu à celle ci-dessous, si elle avait été autorisée:

// Partial specialization is not allowed by the spec, though!
template <typename T> 
inline T const& max<T,T> (T const& a, T const& b)
{                  ^^^^^ <--- [supposed] specializing here
  return 10;
}

Dans le cas d'un modèle de fonction, seule la spécialisation complète est autorisée par le standard C ++, - à l'exclusion des extensions du compilateur!


1
@Narek, la spécialisation partielle des fonctions ne fait pas partie du standard (pour quelque raison que ce soit). Je pense que MSVC le prend en charge en tant qu'extension. Peut-être après un certain temps, cela serait également autorisé par d'autres compilateurs.
iammilind

1
@iammilind: Pas de problème. Il semble déjà le savoir. C'est pourquoi il essaie également cela pour le modèle de fonction. Je l'ai donc modifié à nouveau, ce qui est clair maintenant.
Nawaz

19
Quelqu'un qui peut expliquer pourquoi la spécialisation partielle n'est pas autorisée?
HelloGoodbye

2
@NHDaly, cela ne donne pas d'erreur d'ambiguïté car une fonction correspond mieux que l'autre. La raison pour laquelle il sélectionne (T, T)sur (T1, T2)pour (int, int), c'est parce que le premier garantit qu'il y a 2 paramètres et que les deux types sont identiques; ce dernier garantit seulement qu'il y a 2 paramètres. Le compilateur choisit toujours une description précise. Par exemple, si vous devez faire un choix entre 2 descriptions d'une «rivière», laquelle choisiriez-vous? "collecte d'eau" vs "collecte d'eau qui coule".
iammilind

1
@kfsone, je pense que cette fonctionnalité est en cours de révision, donc ouverte à l'interprétation. Vous pouvez vous référer à cette section open-std , que j'ai vue dans Pourquoi le standard C ++ n'autorise-t-il pas la spécialisation partielle des modèles de fonctions?
iammilind

44

Étant donné que la spécialisation partielle n'est pas autorisée - comme l'ont indiqué d'autres réponses -, vous pouvez la contourner en utilisant std::is_sameet std::enable_if, comme ci-dessous:

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, int>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with ints! " << f << std::endl;
}

template <typename T, class F>
inline typename std::enable_if<std::is_same<T, float>::value, void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with floats! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
}

Production:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2

Edit : Au cas où vous auriez besoin de pouvoir traiter tous les autres cas restants, vous pouvez ajouter une définition indiquant que les cas déjà traités ne devraient pas correspondre - sinon vous tomberiez dans des définitions ambiguës. La définition pourrait être:

template <typename T, class F>
inline typename std::enable_if<(not std::is_same<T, int>::value)
    and (not std::is_same<T, float>::value), void>::type
typed_foo(const F& f) {
    std::cout << ">>> messing with unknown stuff! " << f << std::endl;
}

int main(int argc, char *argv[]) {
    typed_foo<int>("works");
    typed_foo<float>(2);
    typed_foo<std::string>("either");
}

Ce qui produit:

$ ./a.out 
>>> messing with ints! works
>>> messing with floats! 2
>>> messing with unknown stuff! either

Bien que cette chose dans tous les cas semble un peu ennuyeuse, puisque vous devez dire au compilateur tout ce que vous avez déjà fait, il est tout à fait faisable de traiter jusqu'à 5 ou quelques spécialisations supplémentaires.


Il n'est vraiment pas nécessaire de le faire car cela peut être géré par une surcharge de fonctions d'une manière beaucoup plus simple et plus claire.
Adrian

2
@Adrian Je ne peux vraiment pas penser à une autre approche de surcharge de fonctions pour résoudre cela. Vous avez remarqué que la surcharge partielle n'est pas autorisée, non? Partagez avec nous votre solution, si vous pensez qu'elle est plus claire.
Rubens

1
existe-t-il un autre moyen de capturer facilement toutes les fonctions basées sur un modèle?
Nick

15

Qu'est-ce que la spécialisation?

Si vous voulez vraiment comprendre les modèles, vous devriez jeter un œil aux langages fonctionnels. Le monde des modèles en C ++ est un sous-langage purement fonctionnel en soi.

Dans les langages fonctionnels, les sélections sont effectuées à l'aide du Pattern Matching :

-- An instance of Maybe is either nothing (None) or something (Just a)
-- where a is any type
data Maybe a = None | Just a

-- declare function isJust, which takes a Maybe
-- and checks whether it's None or Just
isJust :: Maybe a -> Bool

-- definition: two cases (_ is a wildcard)
isJust None = False
isJust Just _ = True

Comme vous pouvez le voir, nous surchargons la définition de isJust.

Eh bien, les modèles de classe C ++ fonctionnent exactement de la même manière. Vous fournissez une déclaration principale , qui indique le nombre et la nature des paramètres. Cela peut être juste une déclaration, ou aussi agir comme une définition (votre choix), puis vous pouvez (si vous le souhaitez) fournir des spécialisations du modèle et leur associer une version différente (sinon ce serait idiot) de la classe .

Pour les fonctions de modèle, la spécialisation est un peu plus délicate: elle entre quelque peu en conflit avec la résolution de surcharge. En tant que tel, il a été décidé qu'une spécialisation se rapporterait à une version non spécialisée, et les spécialisations ne seraient pas prises en compte lors de la résolution de surcharge. Par conséquent, l'algorithme de sélection de la bonne fonction devient:

  1. Effectuer la résolution de surcharge, parmi les fonctions régulières et les modèles non spécialisés
  2. Si un modèle non spécialisé est sélectionné, vérifiez s'il existe une spécialisation qui conviendrait mieux

(pour un traitement en profondeur, voir GotW # 49 )

En tant que telle, la spécialisation des fonctions par modèle est un citoyen de seconde zone (littéralement). En ce qui me concerne, nous serions mieux sans eux: je n'ai pas encore rencontré de cas où l'utilisation d'une spécialisation de modèle ne pourrait pas être résolue avec une surcharge.

S'agit-il d'une spécialisation de modèle?

Non, c'est simplement une surcharge, et c'est très bien. En fait, les surcharges fonctionnent généralement comme nous l'attendons, tandis que les spécialisations peuvent être surprenantes (rappelez-vous l'article de GotW que j'ai lié).


"As such, template specialization of functions is a second-zone citizen (literally). As far as I am concerned, we would be better off without them: I have yet to encounter a case where a template specialization use could not be solved with overloading instead."Que diriez-vous des paramètres de modèle non type?
Jules GM

@Julius: vous pouvez toujours utiliser la surcharge, bien qu'en introduisant un paramètre factice tel que boost::mpl::integral_c<unsigned, 3u>. Une autre solution pourrait également être d'utiliser enable_if/ disable_if, bien que ce soit une autre histoire.
Matthieu M.

7

La spécialisation partielle non-classe et non variable n'est pas autorisée, mais comme indiqué:

Tous les problèmes en informatique peuvent être résolus par un autre niveau d'indirection. —— David Wheeler

L'ajout d'une classe pour transférer l'appel de fonction peut résoudre ce problème, voici un exemple:

template <class Tag, class R, class... Ts>
struct enable_fun_partial_spec;

struct fun_tag {};

template <class R, class... Ts>
constexpr R fun(Ts&&... ts) {
  return enable_fun_partial_spec<fun_tag, R, Ts...>::call(
      std::forward<Ts>(ts)...);
}

template <class R, class... Ts>
struct enable_fun_partial_spec<fun_tag, R, Ts...> {
  constexpr static R call(Ts&&... ts) { return {0}; }
};

template <class R, class T>
struct enable_fun_partial_spec<fun_tag, R, T, T> {
  constexpr static R call(T, T) { return {1}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, int> {
  constexpr static R call(int, int) { return {2}; }
};

template <class R>
struct enable_fun_partial_spec<fun_tag, R, int, char> {
  constexpr static R call(int, char) { return {3}; }
};

template <class R, class T2>
struct enable_fun_partial_spec<fun_tag, R, char, T2> {
  constexpr static R call(char, T2) { return {4}; }
};

static_assert(std::is_same_v<decltype(fun<int>(1, 1)), int>, "");
static_assert(fun<int>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 1)), char>, "");
static_assert(fun<char>(1, 1) == 2, "");

static_assert(std::is_same_v<decltype(fun<long>(1L, 1L)), long>, "");
static_assert(fun<long>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<double>(1L, 1L)), double>, "");
static_assert(fun<double>(1L, 1L) == 1, "");

static_assert(std::is_same_v<decltype(fun<int>(1u, 1)), int>, "");
static_assert(fun<int>(1u, 1) == 0, "");

static_assert(std::is_same_v<decltype(fun<char>(1, 'c')), char>, "");
static_assert(fun<char>(1, 'c') == 3, "");

static_assert(std::is_same_v<decltype(fun<unsigned>('c', 1)), unsigned>, "");
static_assert(fun<unsigned>('c', 1) == 4, "");

static_assert(std::is_same_v<decltype(fun<unsigned>(10.0, 1)), unsigned>, "");
static_assert(fun<unsigned>(10.0, 1) == 0, "");

static_assert(
    std::is_same_v<decltype(fun<double>(1, 2, 3, 'a', "bbb")), double>, "");
static_assert(fun<double>(1, 2, 3, 'a', "bbb") == 0, "");

static_assert(std::is_same_v<decltype(fun<unsigned>()), unsigned>, "");
static_assert(fun<unsigned>() == 0, "");

4

Non. Par exemple, vous pouvez vous spécialiser légalement std::swap, mais vous ne pouvez pas légalement définir votre propre surcharge. Cela signifie que vous ne pouvez pas faire std::swapfonctionner votre propre modèle de classe personnalisé.

La surcharge et la spécialisation partielle peuvent avoir le même effet dans certains cas, mais loin de tout.


4
C'est pourquoi vous mettez votre swapsurcharge dans votre espace de noms.
jpalecek

2

Réponse tardive, mais certains lecteurs tardifs pourraient la trouver utile: parfois, une fonction d'assistance - conçue de manière à pouvoir être spécialisée - peut également résoudre le problème.

Alors imaginons, c'est ce que nous avons essayé de résoudre:

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = new R(x);
    f(r, y); // another template function?
}

// for some reason, we NEED the specialization:
template <typename R, typename Y>
void function<R, int, Y>(int x, Y y) 
{
    // unfortunately, Wrapper has no constructor accepting int:
    Wrapper* w = new Wrapper();
    w->setValue(x);
    f(w, y);
}

OK, spécialisation partielle de la fonction de modèle, on ne peut pas faire ça ... Alors "exportons" la partie nécessaire à la spécialisation dans une fonction d'aide, spécialisons-la et utilisons-la:

template <typename R, typename T>
R* create(T t)
{
    return new R(t);
}
template <>
Wrapper* create<Wrapper, int>(int n) // fully specialized now -> legal...
{
    Wrapper* w = new Wrapper();
    w->setValue(n);
    return w;
}

template <typename R, typename X, typename Y>
void function(X x, Y y)
{
    R* r = create<R>(x);
    f(r, y); // another template function?
}

Cela peut être intéressant surtout si les alternatives (surcharges normales au lieu de spécialisations, la solution de contournement proposée par Rubens, ... - non pas que celles-ci soient mauvaises ou que la mienne soit meilleure, juste une autre) partageraient beaucoup de code commun.

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.