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

Ajout de methodes / spécialisation de templates

9 réponses
Avatar
Vincent Lascaux
Bonjour,

Je travaille sur un projet de traitement d'image. J'ai une classe
Image template sur le type de pixel. La classe propose un certain
nombre de fonctionnalités communes à tous les types (copier, convertir
vers un autre format de pixel, tracer des traits, des cercles...). Il
y a toutefois certaines opérations qui n'ont de sens que pour certains
types de pixels : par exemple calculer le gradient ne peut se faire
(ou du moins avec ma bibliotheque) que sur des images en niveau de
gris.

J'ai imaginé avoir un truc comme ca :
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.

J'ai aussi pensé faire comme ca :

template<class PixType>
class ImageBase
{
public:
// Fonctions communes à tous les types
};

template<class PixType>
class Image : public ImageBase<PixType>
{ };

template<>
class Image<PixGray> : public ImageBase<PixGray>
{
public:
// Fonctions juste pour PixGray
};

mais maintenant je voudrais ajouter une fonction (la même, avec la
même implémentation) à PixRGB, PixYCrCb, et PixHSV (pour info c'est la
séparation en 3 images PixGray pour les channels), et les deux
méthodes précédentes atteignent leur limites.

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//...
}
</if>
<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* ... */)
{
// ...
}
</if>
};

Merci,

--
Vincent

9 réponses

Avatar
Fabien LE LEZ
On Mon, 09 Apr 2007 19:49:48 -0700, Vincent Lascaux
:

template<class PixType>
class Image
{
public:
void Gradient()


Pourquoi la fonction Gradient() devrait-elle être membre ?

J'imagine que cette fonction lit des pixels, puis modifie des pixels.
Et je suppose que la classe a déjà des fonctions publiques qui
permettent de lire et modifier des pixels.

En d'autres termes, Gradient() peut très bien être une fonction libre,
qui fera son boulot en utilisant l'interface publique de Image<>.

template<class PixType>
class Image
{ ... };

void Gradient (Image<PixGray>& image);


Supposons maintenant que tu souhaites rendre la fonction Gradient()
template, pour pouvoir accepter certains types et pas d'autres.
On peut faire ça, indirectement, très facilement :


/// Image.h

void Gradient (Image<PixGray>& image);
void Gradient (Image<AutreType>& image);

/// Image.cpp

template<class PixType> void DoGradient (Image<PixType>& image)
{
// Le code "réel" ici
}

void Gradient (Image<PixGray>& image) { DoGradient (image); }
void Gradient (Image<AutreType>& image) { DoGradient (image); }

Comme la fonction template DoGradient<> est visible uniquement de ton
code (i.e. dans le .cpp), tu es assuré qu'on ne l'appellera qu'avec
les types qui vont bien.

Avatar
Vincent Lascaux
Pourquoi la fonction Gradient() devrait-elle être membre ?


Hum... Effectivement, ca résoud le problème...

Tracer la frontiere entre méthode et fonctions "globales" est assez
flou (ou du moins me semble assez flou).

Gradient est une opération qui s'effectue sur une image, c'est pour
ca que je voulais l'avoir comme une méthode de la classe.

Est ce que Fill(const PixType& color) qui remplit l'image avec une
valeur constante devrait faire parti de la classe Image ou pas ?

Bref, tu serais pour que Image soit très très minimaliste
(constructeur, destructeur, resize et get/set pixel), et que tout le
reste soit des méthodes globales ? C'est une façon de voir les choses,
je suis pas sur que ce soit la plus OO qu'il soit.

--
Vincent

Avatar
Fabien LE LEZ
On Mon, 09 Apr 2007 22:24:23 -0700, Vincent Lascaux :

Note en passant : je déconseille fortement l'usage du mot "méthode"
pour désigner une fonction, au moins dans le cadre du C++. En effet,
il ne semble pas y avoir de consensus sur la définition de ce mot.
Les deux définitions les plus courantes sont "fonction membre" et
"fonction membre virtuelle", mais tu sembles en avoir une troisième,
puisque tu emploies ce mot pour désigner une fonction libre (i.e. non
membre d'une classe).

Tracer la frontiere entre méthode et fonctions "globales" est assez
flou (ou du moins me semble assez flou).


Une fonction membre a accès aux membres privés. Il me semble qu'une
fonction qui n'a pas besoin d'y avoir accès ne devrait pas être
membre.

Par ailleurs, une fonction libre peut être séparée de la classe : si
tu en as beaucoup, tu peux les mettre dans un .h séparé, et n'inclure
ce .h que dans les .cpp qui en ont besoin.
Par exemple, si un .cpp a besoin de la classe Image mais pas de la
fonction Gradient, il peut inclure le .h qui contient Image, mais pas
celui qui contient Gradient -- ça réduit d'autant les ressources
nécessaires pour compiler, et ça rend le code plus simple à
comprendre.

Cf aussi l'article de Sutter à ce sujet :
<http://www.gotw.ca/gotw/084.htm>.

Bref, tu serais pour que Image soit très très minimaliste
(constructeur, destructeur, resize et get/set pixel), et que tout le
reste soit des méthodes globales ? C'est une façon de voir les choses,
je suis pas sur que ce soit la plus OO qu'il soit.


Il me semble que si, au contraire : les variables membres sont
privées, et le moins possible de fonctions y ont accès.
Si une fonction n'a pas besoin d'avoir accès aux membres privés, il ne
faut pas le lui donner.

Ajoutons également que la POO n'est jamais qu'un paradigme parmi
d'autres. En d'autres termes, un outil, qu'il faut utiliser quand on
en a besoin.

Avatar
Franck Branjonneau
Vincent Lascaux écrivait:

Je travaille sur un projet de traitement d'image. J'ai une classe
Image template sur le type de pixel. La classe propose un certain
nombre de fonctionnalités communes à tous les types (copier, convertir
vers un autre format de pixel, tracer des traits, des cercles...). Il
y a toutefois certaines opérations qui n'ont de sens que pour certains
types de pixels : par exemple calculer le gradient ne peut se faire
(ou du moins avec ma bibliotheque) que sur des images en niveau de
gris.

J'ai aussi pensé faire comme ca :

template<class PixType>
class ImageBase
{
public:
// Fonctions communes à tous les types
};

template<class PixType>
class Image : public ImageBase
{ };

template<>
class Image : public ImageBase
{
public:
// Fonctions juste pour PixGray
};


Je lis Image< PixGray > ici.

mais maintenant je voudrais ajouter une fonction (la même, avec la
même implémentation) à PixRGB, PixYCrCb, et PixHSV (pour info c'est la
séparation en 3 images PixGray pour les channels), et les deux
méthodes précédentes atteignent leur limites.


La proposition de Fabien me semble pertinente.

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//...
}
</if>
<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* ... */)
{
// ...
}
</if>
};


Utiliser un type de base ayant plus de paramètres et spécialiser selon la
valeur de ces parmètres :

template< typename _Pixel >
struct BasicBaseImage {

// Common interface
};

template< typename _Pixel, bool _hasGradient,
int _colorChannels >
struct BasicImage;

template<>
template< typename _Pixel, int _colorChannels >
struct BasicImage< _Pixel, true, _colorChannels >
public BasicBaseImage< _Pixel > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

void gradient() const;
};

template<>
template< typename _Pixel, bool _hasGradient >
struct BasicImage< _Pixel, _hasGradient, 3 >
public BasicBaseImage< _Pixel > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

void splitChannels() const;
};

template< typename _Pixel >
struct Image:
public BasicImage< _Pixel, Pixel::hasGradient, Pixel::colorChannels > {

// typedef_s + using_s from BasicBaseImage< _Pixel >

// Can't use using BasicImage<>::splitChannels nor BasicImage<>::gradient ?
// (lazy instanciation)
// Must be qualified.
};

Tu vois le problème : croissance exponentielle du nombre de spécialisations
explicites -- ici il en manque.


Sur le même principe (sans les inconvénients) mais plus lourd ?

template< typename _Pixel >
struct BasicBaseImage {

// Common interface
};

template< typename _Pixel >
struct BasicImage:
public BasicBaseImage< _Pixel >,
public Gradient< Pixel::hasGradient >,
public SplitChannel< Pixel::colorChannels > {

// typedef_s + using_s from BasicBaseImage< _Pixel >
using Gradient< Pixel::hasGradient >::gradient;
using SplitChannel< Pixel::colorChannels >::splitChannel;
};

template< bool _hasGradient >
struct Gradient {

// Not implemented
void gradient() const;
};

template<>
struct Gradient< true > {

void gradient() const;
};

template< int _colorChannels >
struct SplitChannel {

// Not implemented
void splitChannel() const;
};

template<>
struct SplitChannel< 3 > {

void splitChannel() const;
};

--
Franck Branjonneau

Avatar
Mathias Gaunard

Est ce qu'il y a une façon de faire ce que je veux : une sorte de truc
comme ca :
template<class PixType>
class Image
{
public:
<if PixType == PixGray> // Comment ca s'écrit ?
void Gradient()
{
//...
}
</if>


Ce genre de choses peut se faire par exemple avec SFINAE.

typename enable_if<
is_same<PixType, PixGray>,
void>::type
Gradient()
{
}


<if PixType in { PixRGB, PixYCrCb, PixHSV } >
// ou mieux: <if PixType::cChannels == 3>
void SplitChannels(/* ... */)
{
// ...
}
</if>


typename enable_if<
or_<
is_same<PixType, PixRGB>,
or_<
is_same<PixType, PixYCrCb>,
is_same<PixType, PixHSV>

,
void>::type

SplitChannels(/* ... */)
{
}

typename enable_if<
contains<
vector<PixRGB, PixYCrCb, PixHSV>,
PixType
,
void>::type

SplitChannels(/* ... */)
{
}

typename enable_if<
PixType::cChannels == 3,
void>::type
SplitChannels(/* ... */)
{
}

Avatar
Loïc Joly
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.


Une fonction template n'est instanciée que si elle est utilisée. Donc ta
fonction ne sera que dans des classes où elle est utilisable. Et avoir
un truc qui ne compile pas me semble le meilleur niveau d'erreur qu'on
puisse obtenir.

Juste 1 ou 2 points : Que se passe-il si tu ajoutes une fonction
permettant de convertir un Image<ColorPix*> en Image<PixGray*> ?
Plutôt que de copier le tableau, pourquoi ne pas prendre une référence
dessus ? Voire même avoir un contrôle statique ne faisant rien au
runtime (boost::static_assert, avec boost::type_traits::is_same, devrait
pouvoir t'aider).

Le truc avec enable_if me semble en l'occurence beaucoup trop malin, et
en plus apparait dans la déclaration, ce qui à mon avis l'embrouille.

--
Loïc

Avatar
Loïc Joly

template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.



Une fonction template n'est instanciée que si elle est utilisée. Donc ta
fonction ne sera que dans des classes où elle est utilisable. Et avoir
un truc qui ne compile pas me semble le meilleur niveau d'erreur qu'on
puisse obtenir.


Juste une précision : C'est déjà la méthode largement utilisée par la
STL, comme dans std::list::sort, ou dans le constructeur de vector qui
prend uniquement une taille. Ca pourra certainement s'exprimer plus
clairement quand il y aura des concepts en C++ (bientôt j'espère), mais
en attendant, je ne suis pas trop pour les astuces à base de template
qui alourdissent le code plus qu'elles ne le clarifient.

--
Loïc


Avatar
Mathias Gaunard
template<class PixType>
class Image
{
public:
void Gradient()
{
const Image<PixGray*> compileTimeError = this;
}
};
mais c'est pas vraiment satisfaisant d'avoir la fonction dans toutes
les classes avec un truc qui compile pas si on tente de l'appeler.


Une fonction template n'est instanciée que si elle est utilisée.


Gradient n'est pas une fonction template, mais une fonction membre d'une
classe template.


Avatar
Loïc Joly


Gradient n'est pas une fonction template, mais une fonction membre
d'une classe template.


Effectivement, problème de vocabulaire. Il n'en reste pas moins qu'il
n'y aura pas instanciation sauf si nécessaire.

14.7.1.9
An implementation shall not implicitly instantiate a function
template, a member template, a nonvirtual member function, a member
class or a static data member of a class template that does not
require instantiation.



--
Loïc