Twitter iPhone pliant OnePlus 11 PS5 Disney+ Orange Livebox Windows 11

Spécialisation de template et spécialisation de méthode

19 réponses
Avatar
flure
Bonjour,

Je débute un peu en C++ et notamment pour ce qui concerne les templates.
Je me base sur la documentation que je trouve sur internet pour me
former (notamment http://www.cplusplus.com).
Aujourd'hui, j'ai un problème que je n'arrive pas à résoudre malgré mes
recherches.

Je crée une classe template Matrix qui doit me servir à manipuler des
matrices, ainsi :

templace<int COLS, int ROWS, class T = float>
class Matrix
{
private:
std::vector<T> mData;
public:
// plusieurs méthodes
// ...

// la méthode qui me pose problème :
void setIdentity();
};

T est là pour permettre de choisir entre float et double, idéalement
j'aurais préféré m'en passer, car il n'y a pas vraiment d'intérêt
(mathématique) à manipuler des matrices de TrucMuches ou autres types
exotiques.

La méthode setIdentity() ne peut fonctionner que lorsque T est float ou
double. Elle initialise toutes les données de mon vector à 0.0 sauf la
diagonale qui est initialisée à 1.
De plus ROWS et COLS doivent être égaux (matrice carrée).

Par exemple pour une Matrix<2, 2, float>, setIdentity ferait cela :

for(unsigned y = 0; y < ROWS; y++)
{
for(unsigned x = 0; x < COLS; x++)
{
if(x == y) mData[x + y * COLS] = 1.0f;
else mData[x + y * COLS] = 0.0f;
}
}

Je voudrais que pour le cas où T n'est ni float ni double cette méthode
ne fasse rien, et pour le cas de float elle utilise 1.0f et 0.0f alors
que pour double elle utiliserait 1.0 et 0.0.

Je pensais spécialiser cette méthode pour cela, mais je ne trouve pas la
bonne syntaxe.

Parmi tant d'autres syntaxes j'ai essayé celle-ci :
template <int ROWS, int COLS, T>
void Matrix<ROWS, COLS, float>::setIdentity()
{
for(unsigned row; row < ROWS; row++)
{
for(unsigned col; col < COLS; col++)
{
if(row == col) mData[col + row * COLS] = 1.0f;
else mData[col + row * COLS] = 0.0f;
}
}
}

Mais évidemment cela ne marche pas.
Donc maintenant j'en ai assez de tâtonner, et je viens chercher l'aide
de ceux qui maîtrisent le sujet. ;)

Merci d'avance pour votre aide, et surtout s'il s'avère que je suis
parti sur une mauvaise piste lors de la création de cette classe,
n'hésitez pas à me le faire savoir...

Florent

9 réponses

1 2
Avatar
flure
Il vaudrait mieux prendre des classes
génériques plus originales, par exemple pour utiliser les idiomes SFINAE
(Substitution Failure Is Not An Error), traits, policy ou encore CRTP
(Curiously Recurring Template Pattern).



Rien compris :D
Qu'est-ce que c'est SFINAE, trais, policy et CRTP ??
Avatar
Wykaaa
flure a écrit :
Il vaudrait mieux prendre des classes
génériques plus originales, par exemple pour utiliser les idiomes SFINAE
(Substitution Failure Is Not An Error), traits, policy ou encore CRTP
(Curiously Recurring Template Pattern).



Rien compris :D
Qu'est-ce que c'est SFINAE, trais, policy et CRTP ??



Des idiomes du langages C++.

Pour les traits et policy (politique), voir ici pour une introduction :
http://alp.developpez.com/tutoriels/traitspolicies/

SFINAE :
http://www.semantics.org/once_weakly/w02_SFINAE.pdf
http://www.eggheadcafe.com/searchform.aspx?search=SFINAE
CRTP :
http://www.eggheadcafe.com/searchform.aspx?search=CRTP

En français, sur les templates :
http://cpp.developpez.com/faq/cpp/?page=templates
Avatar
flure
On 21/08/2011 23:28, Wykaaa wrote:
Des idiomes du langages C++.

Pour les traits et policy (politique), voir ici pour une introduction :
http://alp.developpez.com/tutoriels/traitspolicies/

SFINAE :
http://www.semantics.org/once_weakly/w02_SFINAE.pdf
http://www.eggheadcafe.com/searchform.aspx?search=SFINAE
CRTP :
http://www.eggheadcafe.com/searchform.aspx?search=CRTP

En français, sur les templates :
http://cpp.developpez.com/faq/cpp/?page=templates




Encore de la doc à potasser, merci !

Florent
Avatar
James Kanze
On Aug 21, 12:51 pm, Fabien LE LEZ wrote:
On Sun, 21 Aug 2011 01:20:28 +0200, flure :

>template<int COLS, int ROWS, class T = float>

Pourquoi le type "int" ici ?



Parce que « int », c'est le type entier par defaut en C++, et
qu'il n'y a pas de bonne raison de faire autrement.

D'autant que tu utilises "unsigned" plus
loin :

>for(unsigned y = 0; y < ROWS; y++)

Logiquement, ton compilateur doit râler.



Pourquoi est-ce que le compilateur râlera ? « ROWS » est un
constant. Tu veux que le compilateur râle si on écrit :

for ( unsigned y = 0; y < 42; ++ h)

?

Mieux vaut mettre "unsigned int" partout, puisqu'une matrice
avec un nombre négatif de colonnes, ça n'a aucun sens.



Mieux vaut utiliser « int » partout, au moins qu'on désire
l'arithmétique modulo ou fait des opérations bit-à-bit.

[... Je suis d'accord avec toi ; implémenter la fonction
toujours, avec 0 et 1.]
Pour répondre directement à ta question : à ma connaissance,
spécialiser ainsi une fonction membre n'est pas possible. Il faudrait
spécialiser toute ta classe.



On peut bien spécialiser une fonction membre (si je me rappelle
correctement), mais dans ce cas-ci, ce qu'il lui faudrait, c'est
une spécialisation partielle (puisqu'autrement, la
spécialisation ne serait que pour une dimension précise). Et la
spécialisation partielle, ça n'existe pas pour les fonctions.
Membre ou non.

Pour faire les choses proprement, il faudrait commencer par le
commencement : est-il bien utile que setIdentity() soit une fonction
membre ?



Est-ce qu'il est bien utile qu'il y ait une telle fonction ? Je
verrais plutôt un constructeur spécialisé. Quelque chose du
genre :

template <int rows, int columns, typename T = double>
class Matrix
{
// ...
public:
// Special types of matrices...
enum Identity { identity };
// ...
Matrix( Identity )
{
for ( int i = 0; i < rows; ++ i ) {
for ( int j = 0; j < columns; ++ j ) {
data[i][j] = (i == j ? 1 : 0);
}
}
}
// ...

Peut-être que je me trompe, mais je vois mal le cas où on
voudrait construire une Matrix, et ensuite changer sa valeur en
matrice d'identité.

J'ai aussi mes doutes en ce qui concerne la validité de cette
opération quand la matrice n'est pas carrée. Je verrais bien
peut-être une spécialisation partielle pour les matrices
carrées.

--
James Kanze
Avatar
James Kanze
On Aug 21, 2:43 pm, Fabien LE LEZ wrote:
On Sun, 21 Aug 2011 15:26:57 +0200, flure :

>donc c'est quand même un peu difficile de déterminer ce qui
>doit être sorti de la classe et ce qui doit y rester.

C'est au contraire très simple : si tu peux écrire ta fonction en tan t
que fonction libre (i.e. tu n'as pas besoin d'accéder directement aux
données membres ou aux fonctions privées), il faut le faire.



Ça, c'est peut-être un peu simpliste comme logique. Si tu as une
bonne conception de l'abstraction derrière, la classe, alors, ce
qui doit être membre doit être assez évident. (Est-ce que
« changer sa valeur en celle d'une matrice d'identité est une
opération fondamentale des matrices ?)

Une des raisons on lit tellement sur la question, évidemment,
c'est qu'il existe des classes dont l'abstraction soujacent
n'est pas si claire. std::string, par exemple.

>En fait dans l'idéal j'aurais préféré limiter l'utilisation de c ette
>classe aux types float et double. Je n'arrive pas à imaginer du calcul
>matriciel sur autre chose

Ben... par exemple, long double. Ou bien, complex<double>.
Certains calculs sur des matrices d'entiers ont aussi du sens.



Sans parler de BigFloat, Rational, ou d'autres classes qui
implémentent l'abstraction d'une valeur numérique.

Si mes souvenirs sont bons, j'ai aussi fait du calcul
matriciel sur des polynômes.



Là, en revanche, il n'est pas certain qu'une classe Polynome ait
un constructeur qui prend un simple « int » (comme « 1 »).

La question est surtout : en quoi est-ce gênant que la classe
fonctionne sur autre chose que des double ou des float ?

>Il faut savoir aussi que je crée cette classe dans le cadre du
>développement d'un moteur 3D (oui je n'en suis qu'aux prémisses), do nc
>de toute façon seuls float et double seront nécessaires.

Es-tu absolument sûr que ta classe ne te servira pas pour
autre chose un jour ?



Est-ce qu'une classe qui s'appelle Matrix ne doit être
utilisable que dans le cadre d'un moteur 3D ?

--
James Kanze
Avatar
Fabien LE LEZ
On Sun, 21 Aug 2011 16:25:31 -0700 (PDT), James Kanze
:

Là, en revanche, il n'est pas certain qu'une classe Polynome ait
un constructeur qui prend un simple « int » (comme « 1 »).



C'est discutable. Après tout, "1" est un polynôme.
Avatar
Fabien LE LEZ
On Sun, 21 Aug 2011 16:15:36 -0700 (PDT), James Kanze
:

Mieux vaut utiliser « int » partout,



Utiliser "unsigned int" (voire size_t) pour une taille me paraît plus
logique. Même si le résultat est le même, le "unsigned" sert, au
minimum, de documentation.

Par ailleurs, ça simplifie la vérification de bords :

int i;
if (i >= 0 && i < size())
{
// accéder à v[i];
}

vs

unsigned int u;
if (u < size())
{
// accéder à v[u];
}


On peut bien spécialiser une fonction membre (si je me rappelle
correctement),



Spécialiser une fonction template membre d'une classe me paraît
faisable (quoique...), mais je ne savais pas qu'on pouvait spécialiser
une fonction non-template membre d'une classe template.
Pourrais-tu confirmer, et m'indiquer la syntaxe ?


J'ai aussi mes doutes en ce qui concerne la validité de cette
opération quand la matrice n'est pas carrée.



D'où l'utilité d'une fonction libre :

template <unsigned int ROWS, class T>
void setIdentity (Matrix<ROWS,ROWS,T>& m)

La fonction setIdentity() n'est définie que pour une matrice carrée.

(Mais bon, je suis d'accord avec toi, il serait plus logique d'avoir
un constructeur spécial.)
Avatar
flure
On 22/08/2011 19:50, Fabien LE LEZ wrote:
(Mais bon, je suis d'accord avec toi, il serait plus logique d'avoir
un constructeur spécial.)



En fait, un constructeur spécial, c'est une super idée, je vais
l'implémenter.

Merci, continuez le débat ! :)
Avatar
James Kanze
On Aug 22, 6:50 pm, Fabien LE LEZ wrote:
On Sun, 21 Aug 2011 16:15:36 -0700 (PDT), James Kanze
:

>Mieux vaut utiliser « int » partout,

Utiliser "unsigned int" (voire size_t) pour une taille me paraît plus
logique. Même si le résultat est le même, le "unsigned" sert, au
minimum, de documentation.



Tu le trouve plus logique ? Le « unsigned » dans le C++ n'a pas
la sémantique voulue pour une taille. Prenons, par exemple,
l'expression pour trouver la difference entre deux tailles :
« abs( taille1 - taille2 ) ». Avec « unsigned », qu'est-ce que
ça donne ?

Quant à la documentation, « unsigned » signale deux choses : ou
bien, qu'on a besoin ou qu'on veut une arithmétique modulo, ou
bien qu'on considère la valeur plutôt comme un ensemble de bits
que comme une valeur numérique.

Par ailleurs, ça simplifie la vérification de bords :

int i;
if (i >= 0 && i < size())
{
// accéder à v[i];
}

vs

unsigned int u;
if (u < size())
{
// accéder à v[u];
}



Au contraire, ça rend la vérification des bords trompeuse. Que
se passe-t-il quand l'indice est le résultat d'une expression,
et que par erreur, il est négatif ?

Le problème de base, c'est que l'« unsigned » ne se comporte pas
comme un sous-étendu des entiers, et que les conversions
implicites violent toutes les règles mathématiques.

>On peut bien spécialiser une fonction membre (si je me rappelle
>correctement),

Spécialiser une fonction template membre d'une classe me paraît
faisable (quoique...), mais je ne savais pas qu'on pouvait spécialiser
une fonction non-template membre d'une classe template.
Pourrais-tu confirmer, et m'indiquer la syntaxe ?



Pas d'ici:-) ; je n'ai pas ma copie de la norme ici. Et à vrai
dire, je ne suis pas sûr. La syntexe logique serait :

template<> void MaClasse<int>::f();

Mais c'est tout à fait possible qu'il y a des raisons pourquoi
ça ne serait pas admis.

>J'ai aussi mes doutes en ce qui concerne la validité de cette
>opération quand la matrice n'est pas carrée.

D'où l'utilité d'une fonction libre :

template <unsigned int ROWS, class T>
void setIdentity (Matrix<ROWS,ROWS,T>& m)

La fonction setIdentity() n'est définie que pour une matrice carrée.



Par exemple. Mais je verais mieux une fonction qui renvoie une
telle matrice, plutôt qu'une qui convertit une matrice existante
en matrice d'identité.

(Mais bon, je suis d'accord avec toi, il serait plus logique d'avoir
un constructeur spécial.)



Avec une assertion statique pour s'assurer qu'il ne peut servir
que pour des matrices carrées.

--
James Kanze
1 2