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

Surcharge de fonction template et classe de base

7 réponses
Avatar
Michael Doubez
Bonjour,

J'ai le cas d'avoir =E0 appliquer une op=E9ration g=E9n=E9rqie qui marche d=
ans
le cas g=E9n=E9ral et pas dans des cas particulier. Donc j'ai utilis=E9 le
syst=E8me 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=E9e 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 =E0 la
r=E9solution de la surcharge.

Id=E9alement, j'aimerais un m=E9chanisme simple qui permette =E0 un
utilisateur de d=E9finir l'op=E9ration pour sa classe de types. C'est =E0
dire qu'il n'ait =E0 d=E9finir qu'une fois sont interface et la surcharge
de fun().

Toutes les solutions que j'ai envisag=E9es =E0 coup de m=E9ta-programmation
me semblent laides.

Quelqu'un aurait une id=E9e ?

--
Michael

7 réponses

Avatar
Fabien LE LEZ
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 ?
Avatar
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 ?



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*".
Avatar
Michael Doubez
On 26 jan, 12:48, Fabien LE LEZ wrote:
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
Avatar
James Kanze
xOn Jan 26, 1:09 pm, Michael Doubez wrote:
On 26 jan, 12:48, Fabien LE LEZ wrote:



> 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
Avatar
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.
Avatar
Michael Doubez
On 27 jan, 01:29, James Kanze wrote:
xOn Jan 26, 1:09 pm, Michael Doubez wrote:



> On 26 jan, 12:48, Fabien LE LEZ wrote:
> > 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
Avatar
Michael Doubez
On 27 jan, 09:11, Fabien LE LEZ wrote:
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