Cf. déjà dit, le proto supportait l' ``héritage conditionnel'' ; depuis
cet été
j'ai seulement défini la liste des abstractions correspondant aux images
(ex : abstract::greylevel_image)
un objectif est de pouvoir définir des morpheurs génériques le plus
simplement possible. Le problème est double pour un tel morpheur :
- avoir l'héritage qui va bien (résolu par l'utilisation des propriétés)
- ne pas avoir à recoder les méthodes que le morpheur doit fournir.
un exemple : les valeurs d'une image (ref) sont vues à travers une
fonction (f) => une image morphée (ima). ima[p] renvoit donc f(ref[p]).
on doit pouvoir appeler ima.size() pour accéder aux dimensions de
l'image vue à travers f mais on n'a pas envie d'écrire cette méthode.
si ce type de morpheur est implémenté par la classe fimage<I,F> avec
I type de ref et F type de f, on doit redéfinir "op[]" et "op[]
const"
mais on ne veut pas coder fimage<I,F>::size(). En effet, d'après la
description de ce morpheur, la méthode "size" n'est pas transformée
(on doit avoir "ima.size()" est équivalent à "ref.size()). Pire, on
ne connaît pas la liste des méthodes que l'on devraient écrire !
Si les données de ref sont stockées avec un buffer mémoire linéaire, on
peut appeler ref.buffer() pour récupérer son adresse de début et
"ref.buffer_size()" pour connaître sa taille (longueur). On doit donc
pouvoir faire de même avec ima. En revanche, si ref n'est pas
"linéaire", ima ne l'est pas non plus. Comment demander au programmeur
qui doit définir la classe paramétrée "fimage" (et ses méthodes) de
gérer tous ces cas ?
soluce
dans le proto, on définit des hiérarchies d'abstractions ; par exemple
abstract::image dont un bout est donné ci-dessous :
template <typename E>
struct image : public internal::get_image_impl < image<E>, E >
{
unsigned npoints() const // abstract
{
return this->exact().impl_npoints();
}
//...
};
ici npoints() est une méthode abstraite et on code la liaison à la main
(via this->exact().impl_)
ce qui change par rapport à "avant" (hier et les jours encore avant),
c'est le fait qu'on puisse récupérer des implémentations par défaut pour
les méthodes. elles arrivent via l'héritage de internal::get_image_impl
(notez le "get").
bien sûr, il faut écrire les implémentations par défaut (le "set" qui
correspond au "get") :
template <typename E>
struct set_image_impl < image<E>, E> : public virtual image_impl<E>
{
unsigned impl_npoints() const
{
return this->delegate().npoints();
}
//...
};
vous voyez qu'ici, ce n'est pas "exact()" (liaison) qui est appelé mais
"delegate()" (délégation). On ne rêve pas, une implémentation par
défaut signifie qu'on a un objet concret quelque part qui nous fournit
cet implémentation. Dans le cas d'un morpheur (ima), c'est bien sûr
l'image morphée (ref).
au bout du compte, un morpher "identité" (un morpheur qui ne change rien
à l'image ciblée) s'écrit en quelques lignes :
template <typename I> struct id_morpher;
template <typename I>
struct props < id_morpher<I> > : public props <I> {
typedef I delegated_type;
};
template <typename I>
struct id_morpher
: public abstract::from_image_morpher< I, id_morpher<I> >
{
typedef abstract::from_image_morpher< I, id_morpher<I> > super;
id_morpher(const I& ref) : super(ref) { exact_ptr = this; }
};
et pour vérifier que ça marche, on définit ref puis on appelle des
méthodes attendues naturellement :
int main()
{
image2d<int> ref(16, 64);
std::cout << ref.npoints() << std::endl;
// le morpheur qui en fout pas une :
id_morpher< image2d<int> > ima(ref);
std::cout << ima.npoints() << std::endl; // comme toute image
std::cout << ima(5, 1) << std::endl; // car ref est 2d
std::cout << ima.buffer_size() << std::endl; // car ref a un buf.lin.
}
Show replies by date