[C++] function template specialization

Ce code ne compile pas:

[code]struct A {
void foo() {}
};

struct B : A {
void foo(int) {}
};

template void DoFoo(T & x) { x.foo(); }
template<> void DoFoo(A & a) { a.foo(); }

int main() {
B b;
DoFoo(b);
return 0;
}[/code]

La raison en est que lors de l’appel de DoFoo(b), la template DoFoo génère DoFoo au lieu d’utiliser la spécialisation de la classe de base. On peut facilement résoudre le problème dans ce cas précis en étant explicite “DoFoo(b);”.

Mais c’est très gênant. Car cela signifie que si on dérive un type pour lequel il existe une spécialisation, on retombe dans le cas générique. Parfois ça compile carrément pas, comme dans l’exemple, mais parfois ça peut compiler et mener à des résultats imprévus et là c’est dangereux.

Quelqu’un a-t-il une solution pour ce problème ?

struct… c++ … ?

une struct qui dérive d’une struct ca me choque, j’ai jamais fait comme ça (je précise tout de suite que je n’ai pas la science infuse :stuck_out_tongue: )
(et certes, je ne réponds pas à la question).

[quote name=‹ alt3 › date=’ 12 Jul 2005, 15:01’]struct… c++ … ?
une struct qui dérive d’une struct ca me choque, j’ai jamais fait comme ça (je précise tout de suite que je n’ai pas la science infuse :stuck_out_tongue: )
(et certes, je ne réponds pas à la question).
[right][post=« 376715 »]<{POST_SNAPBACK}>[/post][/right][/quote]
En C++ class et struct sont équivalent, à ceci près que les attributs et membres d’une struct sont publics par défaut.

Drealmer> si tu pouvais nous expliquer pourquoi tu as absolument besoin des templates ce serait utile pour éventuellement t’aider… parce que dans ton exemple on voit pas trop l’intérêt. As-tu accès au code des classes susceptibles d’être passées en paramètres de DoFoo ?

Tu écris :

[code]struct B : A
{
  void foo(int) {}

  void foo(void)
  {
      A::foo();
  }
};[/code]

:stuck_out_tongue:
:stuck_out_tongue:

Fais chier, je me suis déjà posé la question mais impossible de retrouver ma « conclusion ». Si ça me revient, je reposterais.

Pour apporter quelques précisions, disons que je bosse sur un petit système de sérialisation en C++. L’idée est de pouvoir lire et écrire n’importe quoi sans modification du code du n’importe quoi en question.

Cependant, il se peut que certaines classes aient une façon plus intelligente de se sérialiser, pour faire en sorte que ce soit standard, je définis une interface:

struct ISerial { virtual void * WriteOut(int & size) = 0; };
Et ensuite une classe qui implémente cette interface:

struct A : ISerial { virtual void * WriteOut(int & size) { &nbsp;size = int(data.size()); &nbsp;return &data.front(); } std::vector<int> data; };
Pour pouvoir indiféremment utiliser WriteOut(x) sur tout type de data, héritant ou pas de mon interface, je pensais écrire une spécialisation de ma template:

template<> void WriteOut(ISerial & t) { int size; void * p = t->WriteOut(size); my_write(p, size); }
Mais l’ennui est que ce n’est appelé que pour les variables dont le type est exactement ISerial… ce qui d’ailleurs ne peut pas exister, vu que c’est une interface.

Donc je me retrouve un peu bloqué. De toutes façons je me suis rendu compte il y a peu qu’un autre problème de design bien plus grave m’empêchait de continuer dans cette voie, je vais m’orienter vers autre chose. Quoi qu’il en soit, je suis curieux de voir si il est possible de trouver une solution élégante à ce problème.

Oh, et aussi je sais aussi qu’il existe des tonnes de librairies de sérialization en C++, dont boost. Mais là je m’amuse à le faire moi-même comme exercice.

Dis, y’aurait pas une couille avec ton “foo()” et “foo(int)” qui se cachent l’un l’autre entre la classe A et la classe B ?
Egalement, à moins que je me trompe, tu as fait hériter B de A en privé (vu que tu n’as pas spécifié “public A”). Donc, la fonction template n’en sait rien que B dérive de A, et du coup il ne peut pas utiliser la méthode overloadée. D’ailleurs, y’a même pas vraiment d’overload puisque les 2 “foo” diffèrent d’un paramètre.

Tu devrais essayer de tester le comportement que tu cherches (polymorphisme d’une fonction template) sur un cas un poil plus simple (sans les 2 points que j’ai décrit ci-dessus).

« foo() » et « foo(int) » ne se cachent pas l’un l’autre, c’est de la surcharge de méthode tout ce qu’il y a de plus légale. Je pense que Drealmer a rajouté « foo(int) » parce que sinon le compilateur ne « voyait » pas l’erreur (c’est le cas avec g++ chez moi).

nope, struct = héritage public par défaut

[quote name=‘morvinet’ date=’ 12 Jul 2005, 17:12’]“foo()” et “foo(int)” ne se cachent pas l’un l’autre, c’est de la surcharge de méthode tout ce qu’il y a de plus légale. Je pense que Drealmer a rajouté “foo(int)” parce que sinon le compilateur ne “voyait” pas l’erreur (c’est le cas avec g++ chez moi).
[right][post=“376765”]<{POST_SNAPBACK}>[/post][/right][/quote]
Oui, c’est pour forcer le compilateur à faire une erreur, sinon il génère la template et appelle la mauvaise fonction sans qu’on s’en rende compte. C’est le résultat dangereux car semi-invisible dont je parlais dans mon post d’origine.

[quote name=‹ morvinet › date=’ 12 Jul 2005, 08:12’]« foo() » et « foo(int) » ne se cachent pas l’un l’autre, c’est de la surcharge de méthode tout ce qu’il y a de plus légale.
[right][post=« 376765 »]<{POST_SNAPBACK}>[/post][/right][/quote]
Ca depend de ton compilateur. Foo(int) peut cacher Foo() car la surcharge peut etre faite d’abord sur le nom et pas sur la signature complete. Quand tu casse l’ambiguitee en overloadant Foo et en rajoutant Foo(int) alors c’est bon, mais sinon il est legal pour le compilo de t’envoyer bouler parceque en mettant Foo(int) dans une classe filel tu as cache Foo() et que donc B.Foo() n’est pas valable. On vient d’essayer et de confirmer avec lordabdul en live sur le canape de mon salon :stuck_out_tongue: Voir le post qui suit pour plus de details par le monsieur lui meme.

Bon, après investigation, c’est bien “foo(int)” qui cache “foo()” (en tous cas avec le compilo de VS.NET 2005). On a le droit de déclarer un “foo(int)” à côté d’un “foo()” dans la même classe, mais si “foo()” n’est pas overloadé et que “foo(int)” est déclaré dans la classe fille, ça cache le “foo()” de la classe mère.

Bref, ton code compile et produit le comportement voulu si, au choix, on vire le “foo(int)”, ou si on overloade le “foo()”.

Le code suivant (qui prend le 2ème choix):

[code]#include

struct A
{
virtual void foo() { std::cout << “A::foo” << std::endl; }
};

struct B : public A
{
void foo(int i){}
virtual void foo() { std::cout << “B::foo” << std::endl; }
};

template void DoFoo(T & x) { x.foo(); }
template<> void DoFoo(A & a) { a.foo(); }

int main()
{
B b;
DoFoo(b);
A a;
DoFoo(a);
return 0;
}[/code]

…donne la sortie suivante:

B::foo A::foo

Bon Glop, lordabdul, +1 chacun :stuck_out_tongue:

En fait ça ne devrait même pas dépendre du compilo puisque ce comportement est décrit dans la norme (enfin ça c’est un document de travail puisque la « vraie » norme n’est pas consultable librement :stuck_out_tongue: ). Je cite :

Avec l’exemple qui va bien juste après.
Il semble que dans certains cas g++ ferme les yeux sur le problème, comme dans le code de Drealmer si on vire les templates.

En tout cas j’aurais appris un truc aujourd’hui :stuck_out_tongue:

Oui bien entendu, c’est défini par la norme C++, une méthode dans une classe dérivée masque les méthodes de même nom dans les classes parents, quelle que soit la signature. Mais c’est pas là le problème, j’avais en fait rajouté ça pour mettre en évidence le fait que ce soit DoFoo(B & t) qui est instancié lorsqu’on utilise DoFoo(b) et non pas la spécialisation DoFoo(A & t) qui existe pourtant déjà.

Et ceci est dangereux car si on dérive d’une classe pour laquelle il existe une spécialisation de template, on perd cette spécialisation et on retombe dans le cas général. Pour une explication en détail sur ce problème, voir mon autre post dans ce thread.

Ah bah voui mais c’est normal. Passkeu par exemple si tu spécialises aussi « DoFoo », laquelle des 2 spécialisations le compilo doit-il prendre? Et si t’as la classe C qui hérite de B, mais qu’il n’y a pas de spécialisation pour C, doit-il prendre celle de B (sa maman) ou de A (sa grand-mère)? Et je parle même pas des héritages multiples. Ca deviendrait un tel bordel que le compilo te balancerait des insultes à base de « ambiguous function call » à tout va, et tu serais obligé de tout préciser explicitement.
Donc ouaip, t’es foutu, il faut écrire les spécializations des classes filles (qui, ceci dit, peuvent appeller la spécialization de la classe mère, histoire de pas trop dupliquer le code non plus)… Dommage pour toi :stuck_out_tongue:

[quote name=‹ lordabdul › date=’ 13 Jul 2005, 16:06’]Ah bah voui mais c’est normal. Passkeu par exemple si tu spécialises aussi « DoFoo », laquelle des 2 spécialisations le compilo doit-il prendre? Et si t’as la classe C qui hérite de B, mais qu’il n’y a pas de spécialisation pour C, doit-il prendre celle de B (sa maman) ou de A (sa grand-mère)? Et je parle même pas des héritages multiples. Ca deviendrait un tel bordel que le compilo te balancerait des insultes à base de « ambiguous function call » à tout va, et tu serais obligé de tout préciser explicitement.
Donc ouaip, t’es foutu, il faut écrire les spécializations des classes filles (qui, ceci dit, peuvent appeller la spécialization de la classe mère, histoire de pas trop dupliquer le code non plus)… Dommage pour toi :stuck_out_tongue:
[right][post=« 377014 »]<{POST_SNAPBACK}>[/post][/right][/quote]
Oui effectivement, mais je me demandais si il n’existait pas une façon de dire au compilateur « utilise les spécialisations de préférence, et si y’a rien qui va, instancie la template »… Y’a bien des trucs un peu tordus dans le genre de la « dominance » pour l’héritage virtuel, alors pourquoi pas ça.
Le principal problème ici, c’est que la personne qui implémente la classe fille n’a pas de moyen de se rendre compte qu’elle doit spécialiser la template.
Bon en gros les spécialisations de template c’est hyper délicat à utiliser de façon fiable.

[quote name=‘Drealmer’ date=’ 13 Jul 2005, 16:13’]Le principal problème ici, c’est que la personne qui implémente la classe fille n’a pas de moyen de se rendre compte qu’elle doit spécialiser la template.
Bon en gros les spécialisations de template c’est hyper délicat à utiliser de façon fiable.
[right][post=“377016”]<{POST_SNAPBACK}>[/post][/right][/quote]
Bah une spécialisation de template, ça porte bien son nom, c’est une “spécialisation”. Comme dans “un cas spécial”. Si ta spécialisation se généraliste ensuite à tout l’arbre d’héritage d’une classe donnée, c’est plus une spécialisation (on sait pas si y’aura que 2 classes filles ou 372 autres classes!).
Ce que tu essaies de faire, c’est tout bêtement un comportement polymorphique (la classe B passe par le même code d’éxecution que sa classe mère A), donc les templates ne sont peut-être pas indiquées comme ça ici, ou alors utilisées différemment, peut-être… Sinon, je dirais de te rabattre plutot sur un bon vieux design pattern?