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

10 réponses

1 2
Avatar
Marc Guéguen
"flure" a écrit dans le message de
news:4e50413c$0$28932$
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.



Il n'est pas facile de spécialiser dans les cas où ce n'est pas...
Dans ton cas, définit la méthode inline de façon à ce qu'elle ne fasse rien
par défaut et spécialise pour les cas spécifiques

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() {}
};


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()
{
}



la syntaxe correcte est :
template <int ROWS, int COLS> // pas de T !
void Matrix<ROWS, COLS, float>::setIdentity()
{
//....
}
Avatar
flure
On 21/08/2011 11:12, Marc Guéguen wrote:
la syntaxe correcte est :
template <int ROWS, int COLS> // pas de T !
void Matrix<ROWS, COLS, float>::setIdentity()
{
//....
}




Bonjour,

Merci pour ton aide, malheureusement, je viens d'essayer cette syntaxe,
et ça ne compile toujours pas, voici mon message d'erreur :

../src/math3d/matrix.h:85:45: error: invalid use of incomplete type
‘class Matrix<ROWS, COLS, float>’
../src/math3d/matrix.h:47:1: error: declaration of ‘class Matrix<ROWS,
COLS, float>’

Je précise que je suis sous Ubuntu en 64 bits, et que j'utilise GCC
4.5.2, si jamais ça peut influer...

Je devrais sans doute m'acheter un bon bouquin de C++, ça pourrait
m'aider...

Florent
Avatar
Fabien LE LEZ
On Sun, 21 Aug 2011 01:20:28 +0200, flure :

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



Pourquoi le type "int" ici ? D'autant que tu utilises "unsigned" plus
loin :

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



Logiquement, ton compilateur doit râler.

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



méthode



Note en passant : je déconseille l'utilisation du mot "méthode" en C++
car sa définition n'est pas vraiment univoque. Mieux vaut dire
"fonction membre".

Je voudrais que pour le cas où T n'est ni float ni double cette méthode
ne fasse rien,



Mauvaise idée. Si tu appelles une fonction dans un contexte où ça n'a
pas de sens, tu veux obtenir une erreur de compilation.

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 ne vois pas bien l'utilité. Assigne 1 et 0, et tu auras
(gratuitement) une fonction qui marche bien pour tous les types
numériques.

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.

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

Si tu peux implémenter setIdentity() en tant que fonction libre, qui
appelle éventuellement des fonctions membres, fais-le. Inutile de
faire grossir ta classe pour rien.

Par exemple :

template <unsigned int ROWS, class T>
void setIdentity (Matrix<ROWS,ROWS,T>& m)
{
for (unsigned int y = 0; y < ROWS; y++)
{
for (unsigned int x = 0; x < ROWS; x++)
{
m.SetValeur (x, y, (x == y) ? 1 : 0);
}
}
}

Ici, tu as tout ce qu'il te faut :

- La fonction n'est définie que pour une matrice carrée ;
- Si le type T n'est pas un type numérique, assigner 1 donnera une
erreur de compilation.

Bien sûr, il faut que tu aies une fonction membre Matrix::SetValeur().
Avatar
flure
On 21/08/2011 13:51, Fabien LE LEZ wrote:
Ici, tu as tout ce qu'il te faut :

- La fonction n'est définie que pour une matrice carrée ;
- Si le type T n'est pas un type numérique, assigner 1 donnera une
erreur de compilation.



Effectivement, ça marche très bien comme ça, merci.
Et c'est vrai qu'il me semble plus logique de sortir de la classe toutes
les fonctions membres qui ne servent que dans le cadre de la géométrie
(identité, création de matrice de rotation, translation, projection etc...).
Ce qui me chifonne c'est d'avoir plein de fonctions libres, je n'aime
pas beaucoup. Je préfère avoir une classe avec que des fonctions membres
statiques pour faire ces manipulations géométriques sur les matrices.
Mais bon, je suppose que ce n'est qu'une histoire de goût.

Bien sûr, il faut que tu aies une fonction membre Matrix::SetValeur().



En effet, j'avais déjà surchargé operator() spécialement pour cela.

Merci pour l'aide précieuse.

Florent
Avatar
Fabien LE LEZ
On Sun, 21 Aug 2011 14:48:03 +0200, flure :

Ce qui me chifonne c'est d'avoir plein de fonctions libres, je n'aime
pas beaucoup.



Tu viens de Java, non ?

Je te conseille la lecture de cette page :
http://www.gotw.ca/gotw/084.htm
Avatar
flure
On 21/08/2011 15:13, Fabien LE LEZ wrote:
On Sun, 21 Aug 2011 14:48:03 +0200, flure:

Ce qui me chifonne c'est d'avoir plein de fonctions libres, je n'aime
pas beaucoup.



Tu viens de Java, non ?



Non, je viens plutôt de C et Delphi (C à la maison, Delphi au boulot),
qui ne sont pas vraiment des modèles en termes de forte encapsulation ;)
Je fais un peu de Java aussi, c'est vrai.

Pour en revenir à ce que j'ai écrit dans mon message précédent, à savoir
sortir les fonctions membres qui n'ont de sens que dans un contexte
géométrique... bref setIdentity() ne rentre pas trop dans cette
catégorie, 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.
J'aime bien l'idée de traiter la classe comme un réceptacle de données,
avec en fonctions membres uniquement les accesseurs et les opérations
basiques (multiplication, addition, déterminant etc...). Le problème
c'est que dans le cas des matrices setIdentity() fait partie de ces
opérations basiques, contrairement à par exemple setRotation(..),
toQuaternion(..) etc.

En fait dans l'idéal j'aurais préféré limiter l'utilisation de cette
classe aux types float et double. Je n'arrive pas à imaginer du calcul
matriciel sur autre chose, mais c'est peut-être simplement parce que je
n'arrive pas à imaginer les problèmes pour lesquels cela peut être utile.

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), donc
de toute façon seuls float et double seront nécessaires.

Comme je disais dans mon tout premier post, j'ai peut-être simplement un
problème dans le design initial de ma classe.

Bien sûr je pourrais prendre une classe de matrices toute faite, c'est
très facile à trouver sur le net puisque des tas de gens l'ont déjà
faite, mais je me casse la tête sur ce problème aussi dans un but
pédagogique. Les templates m'ont longtemps fait un peu peur, alors j'ai
décidé de les affronter ;)

Je te conseille la lecture de cette page :
http://www.gotw.ca/gotw/084.htm



Merci bien, je vais potasser ça :)

Florent
Avatar
Fabien LE LEZ
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 tant
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.

En fait dans l'idéal j'aurais préféré limiter l'utilisation de cette
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.

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

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), donc
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 ?

Passer du temps à généraliser la classe est contre-productif ; en
revanche, décider que ta classe ne doit pas être utilisée avec int
"parce que c'est comme ça, na !" n'est pas la meilleure approche AMHA.


Bien sûr je pourrais prendre une classe de matrices toute faite, c'est
très facile à trouver sur le net puisque des tas de gens l'ont déjà
faite, mais je me casse la tête sur ce problème aussi dans un but
pédagogique. Les templates m'ont longtemps fait un peu peur, alors j'ai
décidé de les affronter ;)



Le conseil habituel : crée ta propre classe, puis, quand elle est
terminée, jette le code et prends une bibliothèque toute faite.
Avatar
Wykaaa
Fabien LE LEZ a écrit :
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 tant
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.

En fait dans l'idéal j'aurais préféré limiter l'utilisation de cette
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.



Et les matrices booléennes aussi, par exemple pour calculer la fermeture
transitive d'un graphe avec l'algorithme de Warshall.
Et d'autres encore : forme matricielle de grammaires formelles, matrices
représentants des automates à états finis, etc.

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

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), donc
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 ?

Passer du temps à généraliser la classe est contre-productif ; en
revanche, décider que ta classe ne doit pas être utilisée avec int
"parce que c'est comme ça, na !" n'est pas la meilleure approche AMHA.


Bien sûr je pourrais prendre une classe de matrices toute faite, c'est
très facile à trouver sur le net puisque des tas de gens l'ont déjà
faite, mais je me casse la tête sur ce problème aussi dans un but
pédagogique. Les templates m'ont longtemps fait un peu peur, alors j'ai
décidé de les affronter ;)



Le conseil habituel : crée ta propre classe, puis, quand elle est
terminée, jette le code et prends une bibliothèque toute faite.



Vouloir refaire une classe Matrice soi-même pour apprendre les templates
en C++ n'est pas une bonne idée car, justement, on cherche à réutiliser
au maximum ce qui existe. 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).
Avatar
Fabien LE LEZ
On Sun, 21 Aug 2011 17:15:37 +0200, Wykaaa :

Vouloir refaire une classe Matrice soi-même pour apprendre les templates
en C++ n'est pas une bonne idée car, justement, on cherche à réutiliser
au maximum ce qui existe.



Pour pouvoir utiliser ce qui existe, il faut le comprendre. Et en
faire soi-même une version (forcément incomplète) est une bonne idée
pour apprendre.

Il vaudrait mieux prendre des classes génériques plus originales



Ça peut venir dans un deuxième temps.
Avatar
flure
On 21/08/2011 15:43, Fabien LE LEZ wrote:
En fait dans l'idéal j'aurais préféré limiter l'utilisation de cette
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.

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



Oui, ça a un sens pour moi, mais si je n'y ai pas pensé c'est parce que
je développe cette classe dans le cadre de mon moteur 3D, où uniquement
les types réels sont utilisés. (quoique les complexes peuvent être
intéressants, pour le rendu de fractales par exemple).

Mais je suis bien d'accord avec toi, je me suis imposé des limites et
maintenant ces limites me posent des problèmes.

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



Aucune idée ! Mais on ne sait jamais, tu as raison.

Passer du temps à généraliser la classe est contre-productif ; en
revanche, décider que ta classe ne doit pas être utilisée avec int
"parce que c'est comme ça, na !" n'est pas la meilleure approche AMHA.



Oui, je suis tombé dans un travers. Surtout quand on fait des templates,
le principe c'est d'avoir quelque chose de générique (si j'ai bien
compris), il est alors un peu idiot de s'imposer ce genre de limitation.

Le conseil habituel : crée ta propre classe, puis, quand elle est
terminée, jette le code et prends une bibliothèque toute faite.



Tout à fait.
1 2