Surcharge de fonction template et classe de base

Le
Michael Doubez
Bonjour,

J'ai le cas d'avoir à appliquer une opération générqie qui marche d=
ans
le cas général et pas dans des cas particulier. Donc j'ai utilisé le
système classique surcharge:

template<class TGeneralCase>
void fun(TGeneralCase* g)
{
foo(g);
}

Et puis les cas particuliers:
void fun(Bar1* b)
{
bar(b);
}

Mais maintenant, j'aurais besoin d'appliquer la fonction quand le type
d'entrée a une certaine interface:
void fun(Itf* i)
{
toto(i);
}

Avec par exemple:
struct B: Itf{}
B b;
fun(&b);

Seulement, la fonction template est toujours le meilleur candidat à la
résolution de la surcharge.

Idéalement, j'aimerais un méchanisme simple qui permette à un
utilisateur de définir l'opération pour sa classe de types. C'est à
dire qu'il n'ait à définir qu'une fois sont interface et la surcharge
de fun().

Toutes les solutions que j'ai envisagées à coup de méta-programmation
me semblent laides.

Quelqu'un aurait une idée ?

--
Michael
Vidéos High-Tech et Jeu Vidéo
Téléchargements
Vos réponses
Gagnez chaque mois un abonnement Premium avec GNT : Inscrivez-vous !
Trier par : date / pertinence
Fabien LE LEZ
Le #21052921
On Tue, 26 Jan 2010 03:22:15 -0800 (PST), Michael Doubez

Idéalement, j'aimerais un méchanisme simple qui permette à un
utilisateur de définir l'opération pour sa classe de types. C'est à
dire qu'il n'ait à définir qu'une fois sont interface et la surcharge
de fun().



Typiquement, on utilise des traits pour ça, non ?
Fabien LE LEZ
Le #21053121
On Tue, 26 Jan 2010 03:22:15 -0800 (PST), Michael Doubez

fun(&b);



Ch'tite question en passant : pourquoi utiliser des pointeurs et pas
des références ?



Toutes les solutions que j'ai envisagées à coup de méta-programmation
me semblent laides.



Celle-là n'est pas beaucoup plus belle, et je ne sais même pas si elle
fonctionne, mais bon, allons-y :


template <class T> void fun (T* ptr)
{
fun_impl<T> (ptr, ptr);
}

template <class T> void fun_impl (T* ptr, ...) // 1
{
// le cas général
}

template <class T> void fun_impl (T* ptr, Itf*) // 2
{
// le cas spécial
}

L'idée : l'argument "..." n'est utilisé que s'il n'y a pas d'autres
possibilités. Donc, si ptr est convertible en un Itf*, la version (2)
est appelée. Sinon, c'est la version (1).

Rappel : un "Itf const*" n'est pas convertible en un "Itf*".
Michael Doubez
Le #21053801
On 26 jan, 12:48, Fabien LE LEZ
On Tue, 26 Jan 2010 03:22:15 -0800 (PST), Michael Doubez

>fun(&b);

Ch'tite question en passant : pourquoi utiliser des pointeurs et pas
des références ?



Parce que dans le cas réel c'est un pointeur et que c'est pas moi qui
ai décidé l'interface.

Le fait est que le cas général est composé de classes orthogonales qu e
je ne peux pas modifier.

Comme tu l'as dis, une solution à base de traits aurait pu marcher
mais je retombe sur le même problème:
template<class T, class Traits = FunTraits<T> >
void fun(T* t)
{
Traits::fun(t);
}

FunTraits<T> va être la version générique.

Et pas question de mettre les traits dans la classe.

>Toutes les solutions que j'ai envisagées à coup de méta-programmat ion
>me semblent laides.

Celle-là n'est pas beaucoup plus belle, et je ne sais même pas si ell e
fonctionne, mais bon, allons-y :

template <class T> void fun (T* ptr)
{
        fun_impl<T> (ptr, ptr);

}

template <class T> void fun_impl (T* ptr, ...) // 1
{
        // le cas général

}

template <class T> void fun_impl (T* ptr, Itf*) // 2
{
        // le cas spécial

}

L'idée : l'argument "..." n'est utilisé que s'il n'y a pas d'autres
possibilités. Donc, si ptr est convertible en un Itf*, la version (2)
est appelée. Sinon, c'est la version (1).

Rappel : un "Itf const*" n'est pas convertible en un "Itf*".



Je ne vois pas comment je peux échapper à l'indirection
supplémentaire.

Les classes orthogonales sont dans leur propre namespace, je pourrait
peut être jouer la dessus mais je connais pas assez bien la résolution
de nom pour savoir si ça peut résoudre mon best match dans la
resolution de nom.

La version template ratisse trop large. Si tout rate, il me reste mes
petites mains et définir la surcharge pour chacune des classes
orthogonales.

--
Michael
James Kanze
Le #21058511
xOn Jan 26, 1:09 pm, Michael Doubez
On 26 jan, 12:48, Fabien LE LEZ


> On Tue, 26 Jan 2010 03:22:15 -0800 (PST), Michael Doubez
>


> >fun(&b);



> Ch'tite question en passant : pourquoi utiliser des
> pointeurs et pas des références ?



Parce que dans le cas réel c'est un pointeur et que c'est pas
moi qui ai décidé l'interface.



Le fait est que le cas général est composé de classes
orthogonales que je ne peux pas modifier.



Comme tu l'as dis, une solution à base de traits aurait pu
marcher mais je retombe sur le même problème:
template<class T, class Traits = FunTraits<T> >
void fun(T* t)
{
Traits::fun(t);
}



FunTraits<T> va être la version générique.



Et pas question de mettre les traits dans la classe.



Rien n'exige que les traits soit dans la classe. Je verrais bien
quelque chose du genre :

typedef char TrueType;
struct FalseType { char dummy[2]; };

template< typename Derived, typename Base >
class DerivesFrom
{
static TrueType f( Base* );
static FalseType f( ... );
public:
static bool const value = sizeof( f( (T*)0 ) ) == TrueType;
};

template< bool value > class Discriminator {};

void funcHelper( Base* base, Discriminator< true >)
{
// cas spécialisé
}

template< typename T >
void funcHelper( T* obj, Discriminator< false >)
{
// cas générique
}

template< typename T >
void func( T* obj )
{
funcHelper( obj, Discriminator< DerivesFrom< T, MyBase
::value >() );


}

(Enfin, c'est de tête. Il y a probablement pas mal de typos.
Mais je me suis servi de quelque chose de semblable recemment,
et ça a bien fonctionné. Dans la mesure où on n'essaie pas de
l'instancier sur un type incomplet.)

--
James Kanze
Fabien LE LEZ
Le #21059301
On Tue, 26 Jan 2010 16:29:01 -0800 (PST), James Kanze

typedef char TrueType;
struct FalseType { char dummy[2]; };



J'y avais pensé aussi, mais il me semble que l'OP souhaite que
l'utilisateur puisse définir ses propres fonctions fun() spécialisées.
Michael Doubez
Le #21060081
On 27 jan, 01:29, James Kanze
xOn Jan 26, 1:09 pm, Michael Doubez


> On 26 jan, 12:48, Fabien LE LEZ > > On Tue, 26 Jan 2010 03:22:15 -0800 (PST), Michael Doubez
> > > > >fun(&b);
> > Ch'tite question en passant : pourquoi utiliser des
> > pointeurs et pas des références ?
> Parce que dans le cas réel c'est un pointeur et que c'est pas
> moi qui ai décidé l'interface.
> Le fait est que le cas général est composé de classes
> orthogonales que je ne peux pas modifier.
> Comme tu l'as dis, une solution à base de traits aurait pu
> marcher mais je retombe sur le même problème:
> template<class T, class Traits = FunTraits<T> >
> void fun(T* t)
> {
>   Traits::fun(t);
> }
> FunTraits<T> va être la version générique.
> Et pas question de mettre les traits dans la classe.

Rien n'exige que les traits soit dans la classe. Je verrais bien
quelque chose du genre :

    typedef char TrueType;
    struct FalseType { char dummy[2]; };

    template< typename Derived, typename Base >
    class DerivesFrom
    {
        static TrueType f( Base* );
        static FalseType f( ... );
    public:
        static bool const value = sizeof( f( (T*)0 ) ) == T rueType;
    };

    template< bool value > class Discriminator {};

    void funcHelper( Base* base, Discriminator< true >)
    {
        //  cas spécialisé
    }

    template< typename T >
    void funcHelper( T* obj, Discriminator< false >)
    {
        //  cas générique
    }

    template< typename T >
    void func( T* obj )
    {
        funcHelper( obj, Discriminator< DerivesFrom< T, MyBase>:: value >() );

    }

(Enfin, c'est de tête. Il y a probablement pas mal de typos.
Mais je me suis servi de quelque chose de semblable recemment,
et ça a bien fonctionné. Dans la mesure où on n'essaie pas de
l'instancier sur un type incomplet.)



Oui. J'avais envisagé d'utiliser cette technique mais ça ne permet de
discriminer qu'un seul héritage et les solutions plus complexes ne
sont pas vraiment user-friendly.

En réalité je souhaiterais que les règles de résolution s'appliquen t
sans tenir compte de la version template et n'utilisent la version
template qu'en dernier ressort. Et que la technique ne soit à faire
que dans la version template.

Comme ça je peux écrire:
void fun(Interface1* itf)
{
// cette fonction s'applique à tout objet d'une classe dérivée de
Interface1
}

void fun(Interface2* itf)
{
// cette fonction s'applique à tout objet d'une classe dérivée de
Interface2
}

template<class T>
void fun(T* itf)
{
// cette fonction s'applique si il n'y a pas de match
}

L'ideal serait de pouvoir utiliser
void fun(...)
{
// cette fonction s'applique si il n'y a pas de meilleur match
}

mais j'ai besoin du template.


Ton exemple m'a donné une idée, je pourrait jouer sur le type de
retour de la fonction pour identifier si il y a un match:

SpecificType fun(...)
{
assert(0);
}

template<class T>
void funMain(T* t)
{
if( sizeof(fun((T*)0)) != sizeof(SpecificType) ) return fun(t);

// la suite de cette fonction s'applique si il n'y a pas de match
}

--
Michael
Michael Doubez
Le #21060691
On 27 jan, 09:11, Fabien LE LEZ
On Tue, 26 Jan 2010 16:29:01 -0800 (PST), James Kanze

>    typedef char TrueType;
>    struct FalseType { char dummy[2]; };

J'y avais pensé aussi, mais il me semble que l'OP souhaite que
l'utilisateur puisse définir ses propres fonctions fun() spécialisé es.



Finalement, j'ai utilisé une méthode beaucoup plus simple:

template<class R,class Exec>
struct default_exec
{
template<class T>
default_exec(const T& t)
: result(Exec()(t))
{
// NOP
}

operator const R()const
{
return result;
}

const R result;
};

struct DefaultAction
{
template<class T>
T* operator()(T* const t)const
{
// executée si pas de meilleur match
}
};

void* fun(const default_exec<void*,DefaultAction>& t)
{
return t;
}

fun est considérée en dernier car elle necessite l'instantiation d'une
temporaire.

C'est pas parfait (le type de retour ne peut pas dépendre du type
d'entrée) mais ça me suffit.

--
Michael
Publicité
Poster une réponse
Anonyme