web-dev-qa-db-fra.com

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

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> { 
  … 
}; 

De plus, je sais que C++ n'autorise pas la spécialisation partielle du modèle de fonction (seul le type complet est autorisé). 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, pourriez-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;
}
70
Narek

Dans l'exemple, vous êtes réellement surcharge (sans spécialisation) le max<T1,T2> une fonction. La syntaxe de spécialisation partielle aurait dû ressembler un peu comme ci-dessous ( si cela avait été autorisé ):

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

[Remarque: dans le cas d'un modèle de fonction, seule la spécialisation ) complète est autorisé par le standard C++ (à l'exception des extensions du compilateur).]

70
iammilind

Étant donné que la spécialisation partielle n'est pas autorisée - comme le soulignent d'autres réponses -, vous pouvez la contourner à l'aide de std::is_same et 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);
}

Sortie:

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

Edit: Si vous avez besoin de pouvoir traiter tous les autres cas, vous pouvez ajouter une définition indiquant que les cas déjà traités ne doivent 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");
}

Qui produit:

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

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

35
Rubens

Qu'est-ce que la spécialisation?

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

Dans les langages fonctionnels, les sélections sont effectuées à l'aide de correspondance de modèle :

-- 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 constater, nous surcharge la définition de isJust.

Les modèles de classes C++ fonctionnent exactement de la même manière. Vous fournissez une déclaration principale indiquant le nombre et la nature des paramètres. Ce peut être simplement une déclaration, ou agit également 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 est en conflit avec la résolution de la surcharge. En tant que tel, il a été décidé qu'une spécialisation se rapporterait à une version non spécialisée et que les spécialisations ne seraient pas prises en compte lors de la résolution d'une surcharge. Par conséquent, l'algorithme de sélection de la fonction appropriée devient:

  1. Effectuer la résolution de surcharge, parmi les fonctions habituelles 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 tel, la spécialisation de modèle de fonctions est un citoyen de deuxième zone (littéralement). En ce qui me concerne, nous serions mieux sans eux: je n'ai pas encore rencontré le cas où l'utilisation d'une spécialisation de gabarit ne pourrait pas être résolue avec une surcharge.

Est-ce une spécialisation de template?

Non, c'est simplement une surcharge, et c'est bien. En fait, les surcharges fonctionnent généralement comme prévu, alors que les spécialisations peuvent être surprenantes (rappelez-vous l'article GotW que j'ai lié).

13
Matthieu M.

Non. Par exemple, vous pouvez légalement vous spécialiser std::swap, mais vous ne pouvez pas définir légalement votre propre surcharge. Cela signifie que vous ne pouvez pas faire std::swap fonctionne pour 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
Puppy

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

Tous les problèmes informatiques 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, "");
3
user2709407

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

Alors imaginons que 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 template, nous ne pouvons pas le faire ... Donc, "exportons" la pièce nécessaire à la spécialisation dans une fonction d'assistance, 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?
}

Ceci peut être intéressant, en particulier si les alternatives (surcharges normales au lieu de spécialisations, le solution de contournement proposée par Rubens, ... - non pas que ceux-ci soient mauvais ou que le mien soit meilleur, juste un autre un) partagerait pas mal de code commun.

2
Aconcagua