Bonjour,
Je vais citer quelques parties du FDIS (c'est à dire, à peu près, la définition du C++), ce n'est pas très grave si tu ne comprends pas les dites citations, c'est juste pour donner des précisions.
En plus de l'erreur que tu as donnée, il y a d'autres problèmes dans ton code.
Précision sur les templates
Retournons à la définition d'un template (appelé aussi patron ou modèle en français) :

Envoyé par
FDIS (n3290), §14 [temp] ¶1
A template defines a family of classes or functions or an alias for a family of types. […]
Dans ton cas, tu définis une famille de classes point. Il se trouve que les membres de cette famille de classes ont des fonctions membres.
Ta famille de classes point contient par exemple une classe point<int,3> et cette classe point<int,3> a une fonction membre int point<int,3>::getVal(int).
Ton class template (c'est à dire ta famille de classe, ton modèle de classe) a une particularité : il est paramétré par un argument (que tu as appelé N) ayant une valeur par défaut (que tu as fixé à 3).

Envoyé par
FDIS (n3290), §14.1 [temp.param] ¶9
A default template-argument is a template-argument (14.3) specified after = in a template-parameter. A default template-argument may be specified for any kind of template-parameter (type, non-type, template) that is not a template parameter pack (14.5.3). A default template-argument may be specified in a template declaration. A default template-argument shall not be specified in the template-parameter-lists of the definition of a member of a class template that appears outside of the member's class. A default template-argument shall not be specified in a friend class template declaration. If a friend function template declaration specifies a default template-argument, that declaration shall be a definition and shall be the only declaration of the function template in the translation unit.
La mise en gras est de moi on peut lire cette phrase comme :
Un argument par défaut d'un template (3 dans ton code) ne doit pas être spécifié dans la liste de paramètre de template (<class T, int N=3> dans ton code) de la définition d'un membre d'une class template (template <class T, int N=3> T point<T>::getVal(int i){ … } dans ton code) qui apparait hors de la classe du membre (c'est à dire hors de template<class T, int N=3> class point{ … };, ce qui est le cas dans ton code).
Donc, tu ne peut pas spécifier de valeur par défaut pour N dans le code suivant :
1 2 3 4 5
| template <class T, int N = 3>
T point<T>::getVal(int i)
{
return tab[i];
} |
On pourrait croire que c'est dommage, mais en fait non, parce que tu ne peux pas le faire, mais… tu n'as pas besoin de le faire.

Envoyé par
FDIS (n3290), §14.5.1 [temp.class] ¶3
When a member function, a member class, a member enumeration, a static data member or a member template of a class template is defined outside of the class template definition, the member definition is defined as a template definition in which the template-parameters are those of the class template. The names of the template parameters used in the definition of the member may be different from the template parameter names used in the class template definition. The template argument list following the class template name in the member definition shall name the parameters in the same order as the one used in the template parameter list of the member. Each template parameter pack shall be expanded with an ellipsis in the template argument list. […]
Donc, quand tu définis une fonction membre (par exemple getVal) d'un class template (par exemple point) hors de la définition de ce class template (template <class T,int N=3> class point{ /*C'est à dire pas ici.*/ };), alors tu dois définir cette fonction membre comme un template dont les paramètres sont ceux du class template.
La question qui se pose alors est : quels sont les paramètres de ton class template ? T et N, en effet, bien que N ait une valeur par défaut, ça reste un paramètre de ton template. La bonne définition de getVal est donc :
1 2 3 4 5
| template <class T, int N> // Pas de valeur par défaut à cause de §14.1 ¶9
T point<T,N>::getVal(int i) // point a T et N comme argument à cause de §14.5.1 ¶3
{
return tab[i];
} |
Note que le même problème est présent dans tes autres fonctions membres y compris le constructeur.
En fait, tu n'as pas besoin de spécifier de valeur par défaut parce que ce n'est pas ta fonction qui est un function template, mais ta classe. Donc quand getVal est appelé, elle l'est sur un objet qui a déjà une valeur pour N.
Destruction d'un sous objet
Ton class template a un membre :
Et ton destructeur est (après correction) :
1 2 3 4 5
| template <class T, int N>
point<T,N>::~point()
{
delete tab;
} |
Or :

Envoyé par
FDIS (n3290), §3.7.5 [basic.stc.inherit] ¶1
The storage duration of member subobjects, base class subobjects and array elements is that of their complete object (1.8).
C'est à dire que quand tu supprimeras un point, tu supprimeras aussi son membre tab. Tu n'as donc pas besoin d'appeler delete sur tab et à vrai dire tu ne dois pas le faire.
En général, tu appelles delete sur les objets retournés par new. En plus dans ton code, c'est un tableau, dans ce cas on appelle new[] et delete[] quand c'est nécessaire.
Ici, tu n'as besoin ni d'un new[], ni d'un delete[] et encore moins d'un new ou d'un delete.
Donc finalement… tu n'as même pas besoin de définir un destructeur ! :)
Redéfinition d'un argument par défaut d'une fonction
La déclaration du constructeur de point est :
point(T a = 0, T b = 0, T c = 0);
Sa définition (après correction) :
1 2 3 4 5 6 7
| template <class T, int N>
point<T,N>::point(T a = 0, T b = 0, T c = 0)
{
tab[1] = a;
tab[2] = b;
tab[3] = c;
} |
mais les arguments par défaut d'une fonction ne doivent être spécifiés qu'une seule fois :

Envoyé par
FDIS (n3290), §8.3.6 [dcl.fct.default] ¶4
[…] A default argument shall not be redefined by a later declaration (not even to the same value). […]
En plus il y a un cas particulier :

Envoyé par
FDIS (n3290), §8.3.6 [dcl.fct.default] ¶6
[…] Default arguments for a member function of a class template shall be specified on the initial declaration of the member function within the class template. […]
Ça veut dire que tu es obligés de mettre ces arguments par défaut dans la déclaration lors de la définition du class template.
Accès en dehors des limites d'un tableau
Ton constructeur est (après correction) :
1 2 3 4 5 6 7
| template <class T, int N>
point<T,N>::point(T a, T b, T c)
{
tab[1] = a;
tab[2] = b;
tab[3] = c;
} |
Mais que ce passe t il si N=2 ? Sachant que tu n'as pas d'autre constructeur c'est problématique si tu souhaites créer des points sur un plan (avec N=2 donc).
Si tu souhaites conserver se constructeur mais t'assurer qu'il ne sera pas appeler pour N<3, tu peux utiliser static_assert : ça te permet de vérifier lors de la compilation si une condition est vrai :
1 2 3 4 5 6 7 8
| template <class T, int N>
point<T,N>::point(T a, T b, T c)
{
static_assert(N>=3, "Construction d'un point de moins de 3 dimensions avec trois valeurs.");
tab[1] = a;
tab[2] = b;
tab[3] = c;
} |
Cela te permet de trouver les erreurs dès la compilation ! Malheureusement static_assert n'apparait qu'avec C++11 (une version de C++ sortie fin 2011), Boost possède une macro similaire et il est facile de l'écrire en C++03 pur. Mais si ce n'est qu'un code « pour tester », tu n'as pas trop à y faire attention, sache juste que c'est dangereux.
Remarque que ce n'est tout de même pas un très beau constructeur, si tu crées un point à 4 dimensions, seul trois des quatre valeurs seront initialisées.
Utilisation de #pragma
Les directives préprocesseurs #pragma sont utiles si tu ne vises qu'un seul compilateur :

Envoyé par
FDIS (n3290), §16.6 [cpp.pragma] ¶1
A preprocessing directive of the form
# pragma pp-tokensopt new-line
causes the implementation to behave in an implementation-defined manner. The behavior might cause translation to fail or cause the translator or the resulting program to behave in a non-conforming manner.
Any pragma that is not recognized by the implementation is ignored.
Pour garder de bonnes habitudes il est souvent préférable d'utiliser l'include guard habituelle (avec le nom que tu veux) :
1 2 3 4
| #ifndef FILENAME_HPP_INCLUDED
#define FILENAME_HPP_INCLUDED
// Code
#endif |
Version corrigée
Voici le code corrigé, il reste tout de même quelques autres corrections à apporter (notamment sur le constructeur).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30
| #ifndef POINT_HPP_INCLUDED
#define POINT_HPP_INCLUDED
template <class T, int N = 3>
class point
{
private:
T tab[N];
public:
point(T a = 0, T b = 0, T c = 0);
T getVal(int);
};
template <class T, int N>
point<T,N>::point(T a, T b, T c)
{
static_assert(N>=3, "Construction d'un point de moins de 3 dimensions avec trois valeurs."); // Si ton compilateur supporte C++11.
tab[1] = a;
tab[2] = b;
tab[3] = c;
}
template <class T, int N>
T point<T,N>::getVal(int i)
{
return tab[i];
}
#endif |
Excuses moi pour mes mauvaises traductions, je ne connais pas bien le termes français correspondant à certains termes anglais. Bonne chance !
Partager