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

cast & pointeurs de fonction...

9 réponses
Avatar
Frédéric Gourul
Bonjour,

Après quelques expérimentations avec les pointeurs de fonction, j'ai
désormais une question précise sur le sujet.
Considérons les classes suivantes:

class CTest
{
public:
virtual void foo() {cout << "call CTest::foo" << endl;}
virtual int foo2() {cout << "call CTest::foo2" << endl; return 0;}
virtual CTest* foo3() {cout << "call CTest::foo3" << endl; return this;}
};

class CTestD : public CTest
{
public:
virtual CTestD* foo3() {cout << "call CTestD::foo3" << endl; return this;}
};

et que je déclare les pointeurs de fonction suivant:

CTest c; CTestD d;
void(CTest::*pf)() = &CTest::foo;
CTest*(CTest::*pf2)() = &CTest::foo3;

j'aimerai pouvoir transmettre à pf des fonctions ayant une valeur de retour
de n'importe quel type puisque je ne l'exploite pas au travers de mon
pointeur. Et j'aimerai pouvoir transmettre à pf2 des fonctions dont le type
de retour pourraient être des pointeurs sur des classes dérivée de CTest*

si je n'ai pas vraiment de scrupules à écrire:
pf = reinterpret_cast<void(CTest::*)()>(&CTest::foo2); // vivi c'est bien
un reinterpret_cast... pas taper!!!

je ne veux pas écrire:
CTest*(CTestD::*pf3)() =
reinterpret_cast<CTest*(CTestD::*)()>(&CTestD::foo3);
ce qui reviendrait dans ce cas à autoriser tout et n'importe quoi, ce que je
ne veux pas évidemment...

Bien entendu, tout cela est encapsulé dans une classe template dont le type
générique est le type de la valeur retournée par le pointeur de fonction.
Quelqu'un a-t-il un idée pour n'autoriser que la conversion T -> void et les
conversions légales autorisées par static_cast<> ?

Merci pour votre aide, toujours la bienvenue :)

9 réponses

Avatar
Frédéric Gourul
"Yannick Le goc" a écrit dans le message news:
bu6954$jpl$
Tout d'abord, helas dans le standard, il n'y a pas de conversion
implicite d'un type pointeur de fonction vers un autre. Tu n'echapperas
a priori pas au reinterpret_cast.
C'est pas grave, tu peux toujours effectuer un controle de type
dynamique ou statique par les template qui sera cache a l'utilisateur.


J'aimerai bien en effet faire contrôler les types au compilateur, car je
veux qu'une conversion "non autorisée" soit rejetée par le compilateur. Je
n'ai aucune idée de la manière dont on peut faire ca...

On y verrait un peu plus clair si tu donnais le squelette de ta classe
template qui encapsule le tout?


La voilà:

template <class C, typename R>
class PTest
{
private:
C* c;
R(C::*pf)();

public:
PTest(C* c, R(C::*m)()) :c(c), pf(m) {}

template <typename T>
PTest(C* c, T(C::*m)()) :c(c)
{
// contrôles de conversion: T -> R
pf = reinterpret_cast<R(C::*)()>(m);
}

virtual R operator()() {return (c->*pf)();}
};


Et voici de quoi tester. Actuellement, tout est permis par le compilo :(

CTest c;
CTestD d;
PTest<CTest, void> pt0(&c, &CTest::foo); // valide
PTest<CTest, int> pt1(&c, &CTest::foo); // invalide
PTest<CTest, int> pt2(&c, &CTest::foo2); // valide
PTest<CTest, void> pt3(&c, &CTest::foo2); // valide
PTest<CTestD, CTestD*> pt4(&d, &CTestD::foo3); // valide
PTest<CTestD, CTest*> pt5(&d, &CTestD::foo3); // valide
PTest<CTest, CTestD*> pt6(&c, &CTest::foo3); // invalide

Merci pour ton aide:

Avatar
Yannick Le goc
Frédéric Gourul wrote:
Bonjour,

Après quelques expérimentations avec les pointeurs de fonction, j'ai
désormais une question précise sur le sujet.
Considérons les classes suivantes:

class CTest
{
public:
virtual void foo() {cout << "call CTest::foo" << endl;}
virtual int foo2() {cout << "call CTest::foo2" << endl; return 0;}
virtual CTest* foo3() {cout << "call CTest::foo3" << endl; return this;}
};

class CTestD : public CTest
{
public:
virtual CTestD* foo3() {cout << "call CTestD::foo3" << endl; return this;}
};

et que je déclare les pointeurs de fonction suivant:

CTest c; CTestD d;
void(CTest::*pf)() = &CTest::foo;
CTest*(CTest::*pf2)() = &CTest::foo3;

j'aimerai pouvoir transmettre à pf des fonctions ayant une valeur de retour
de n'importe quel type puisque je ne l'exploite pas au travers de mon
pointeur. Et j'aimerai pouvoir transmettre à pf2 des fonctions dont le type
de retour pourraient être des pointeurs sur des classes dérivée de CTest*

si je n'ai pas vraiment de scrupules à écrire:
pf = reinterpret_cast<void(CTest::*)()>(&CTest::foo2); // vivi c'est bien
un reinterpret_cast... pas taper!!!

je ne veux pas écrire:
CTest*(CTestD::*pf3)() > reinterpret_cast<CTest*(CTestD::*)()>(&CTestD::foo3);
ce qui reviendrait dans ce cas à autoriser tout et n'importe quoi, ce que je
ne veux pas évidemment...

Bien entendu, tout cela est encapsulé dans une classe template dont le type
générique est le type de la valeur retournée par le pointeur de fonction.
Quelqu'un a-t-il un idée pour n'autoriser que la conversion T -> void et les
conversions légales autorisées par static_cast<> ?

Merci pour votre aide, toujours la bienvenue :)


Tout d'abord, helas dans le standard, il n'y a pas de conversion

implicite d'un type pointeur de fonction vers un autre. Tu n'echapperas
a priori pas au reinterpret_cast.
C'est pas grave, tu peux toujours effectuer un controle de type
dynamique ou statique par les template qui sera cache a l'utilisateur.
On y verrait un peu plus clair si tu donnais le squelette de ta classe
template qui encapsule le tout?

Yannick

Avatar
Frédéric Gourul
"Yannick Le goc" a écrit dans le message news:
bu6hil$t83$

Tu veux donc controler la conversion de T vers R et accepter certaines
conversions mais pas toutes et ce a la compilation. Ces choix etant
purement arbitraires, une solution est donc de faire planter le compilo
qd on le veut bien.


Ben j'avais pas l'impression que c'était aussi arbitraire que ca:
admettons que ce soit arbitraire pour les pointeurs<void> qui peuvent
accepter des fonctions retournant qqchose, j'admet que c'est un peu
cavalier... je peux m'en passer, mais je trouvais ca bien pratique.
par contre, les autres conversions que je voudrais autoriser, ce n'est que
de l'upcast.

j'ai bien le droit de faire, si B hérite de A:

A* A::getInstance() {return this;}
B* B::getInstance() {return this;}

A* pa = a.getInstance();
A* pb = b.getInstance();

alors naturellement j'aimerai aussi pouvoir faire:

A* (A::* pfa)() = &A::getInstance;
A* pa = (*pfa)();

A* (B::* pfb)() = &B::getInstance;
A* pb = (*pfb)();


Une maniere de le faire(sans doute la seule) est de se baser sur la
specialisation partielle de classe incomplete, mais en fait c'est
complique d'autant que dans ton cas c'est un constructeur qu'il faut
specialiser partiellement, interdit par le standard.
Donc si tu es motive, plonges-toi dans les templates avances ou bien
tout simplement fais une erreur a l'execution!

Yannick

petit exemple pour une classe:



[snip] // exemple à étudier et à essayer de comprendre... :)


=> ici on a decide de n'accepter que A == B pour myclass. Donc
myclass<A, A> sera ok mais myclass<A, B> (A != B) provoquera une erreur
du compilateur (test<A, B, false> non definie).

on peut specialiser plus finement conv pour accepter ou rejeter d'autres
types de couple (A, B) mais ce n'est pas simple!


Effectivement, ca m'a l'air trop compliqué et je pense que ca va trop loin
pour ce que je veux. Si ca avait été possible facilement j'aurai laissé
cette possibilité, mais là je préfère avoir qqchose d'un peu plus
contraignant que quelque chose qui autorise n'importe quoi...

Yannick


Merci encore.
Fred.

Avatar
Yannick Le goc
Frédéric Gourul wrote:
"Yannick Le goc" a écrit dans le message news:
bu6954$jpl$

Tout d'abord, helas dans le standard, il n'y a pas de conversion
implicite d'un type pointeur de fonction vers un autre. Tu n'echapperas
a priori pas au reinterpret_cast.
C'est pas grave, tu peux toujours effectuer un controle de type
dynamique ou statique par les template qui sera cache a l'utilisateur.



J'aimerai bien en effet faire contrôler les types au compilateur, car je
veux qu'une conversion "non autorisée" soit rejetée par le compilateur. Je
n'ai aucune idée de la manière dont on peut faire ca...


On y verrait un peu plus clair si tu donnais le squelette de ta classe
template qui encapsule le tout?



La voilà:

template <class C, typename R>
class PTest
{
private:
C* c;
R(C::*pf)();

public:
PTest(C* c, R(C::*m)()) :c(c), pf(m) {}

template <typename T>
PTest(C* c, T(C::*m)()) :c(c)
{
// contrôles de conversion: T -> R
pf = reinterpret_cast<R(C::*)()>(m);
}

virtual R operator()() {return (c->*pf)();}
};


Et voici de quoi tester. Actuellement, tout est permis par le compilo :(

CTest c;
CTestD d;
PTest<CTest, void> pt0(&c, &CTest::foo); // valide
PTest<CTest, int> pt1(&c, &CTest::foo); // invalide
PTest<CTest, int> pt2(&c, &CTest::foo2); // valide
PTest<CTest, void> pt3(&c, &CTest::foo2); // valide
PTest<CTestD, CTestD*> pt4(&d, &CTestD::foo3); // valide
PTest<CTestD, CTest*> pt5(&d, &CTestD::foo3); // valide
PTest<CTest, CTestD*> pt6(&c, &CTest::foo3); // invalide

Merci pour ton aide:


Tu veux donc controler la conversion de T vers R et accepter certaines

conversions mais pas toutes et ce a la compilation. Ces choix etant
purement arbitraires, une solution est donc de faire planter le compilo
qd on le veut bien.
Une maniere de le faire(sans doute la seule) est de se baser sur la
specialisation partielle de classe incomplete, mais en fait c'est
complique d'autant que dans ton cas c'est un constructeur qu'il faut
specialiser partiellement, interdit par le standard.
Donc si tu es motive, plonges-toi dans les templates avances ou bien
tout simplement fais une erreur a l'execution!

Yannick

petit exemple pour une classe:

template<class T, class U>
struct conv
{
enum {result = false};
};

template<class T>
struct conv<T, T>
{
enum {result = true};
};

template<class A, class B, bool ok>
struct test;

template<class A, class B>
struct test<A, B, true>
{
};

template<class A, class B>
class myclass : public test<A, B, conv<A, B>::result>
{
};

=> ici on a decide de n'accepter que A == B pour myclass. Donc
myclass<A, A> sera ok mais myclass<A, B> (A != B) provoquera une erreur
du compilateur (test<A, B, false> non definie).

on peut specialiser plus finement conv pour accepter ou rejeter d'autres
types de couple (A, B) mais ce n'est pas simple!

Yannick


Avatar
Loïc Joly
Frédéric Gourul wrote:

[...]
j'aimerai pouvoir transmettre à pf des fonctions ayant une valeur de retour
de n'importe quel type puisque je ne l'exploite pas au travers de mon
pointeur.
Tu ne peux pas. Du moins pas directement, mais des classe comme

boost::fonction te permettent de faire comme si.

En l'occurence, pourquoi vouloir réinventer une roue qui tourne à peu
près rond ?
--
Loïc

Avatar
Frédéric Gourul
"Yannick Le goc" a écrit dans le message de
news:bu6hil$t83$

Une maniere de le faire(sans doute la seule) est de se baser sur la
specialisation partielle de classe incomplete, mais en fait c'est
complique d'autant que dans ton cas c'est un constructeur qu'il faut
specialiser partiellement, interdit par le standard.


Juste une question, si ca n'avais pas été un constructeur, la spécialisation
partielle aurait été plus simple ? Car, au final, ca ne sera pas au niveau
du constructeur dont j'en aurais besoin, mais dans la méthode qui connecte
la fonction au foncteur.

Et il y a un moyen pour vérifier à la compilation qu'une classe est bien une
dérivée d'une autre ?

Avatar
Frédéric Gourul
"Loïc Joly" a écrit dans le message de
news:bu73dq$gq0$
Frédéric Gourul wrote:

Tu ne peux pas. Du moins pas directement, mais des classe comme
boost::fonction te permettent de faire comme si.

En l'occurence, pourquoi vouloir réinventer une roue qui tourne à peu
près rond ?


J'étais sur que quelqu'un me dirait cela. Je sais que boost le fait et c'est
d'ailleurs grâce à toi que je le sais, je l'avais vu dans ton exemple (cf.
post d'hier). Mais j'aime comprendre comment les choses fonctionnent, et
réinventer la roue me permet de me fixer les idées lorsque j'utilise qqchose
de nouveau. Et puis ca me permet, lorsque j'utilise une autre roue
(meilleure que la mienne), de savoir qu'elle n'aurait pas fonctionnée si
elle était carrée :)

Fred.

Avatar
Frédéric Gourul
"Yannick Le goc" a écrit dans le message news:
bu84n4$lkm$

constructeur ou methode c'est la meme chose, on ne specialise
partiellement que des classes ou structures.


C'est ce que j'ai vu grâce aux pistes que tu m'as données.
Du coup, j'ai fait une spécialisation partielle de ma classe dans le cas où
le type de retour est "void". C'est une solution, certes un peu lourde, mais
tout à fait satisfaisante pour moi.

Verifier a la compilation
qu'une classe derive d'une autre peut se faire mais la aussi c'est
difficile. Alexandrescu propose dans "modern c++ design" une maniere de
le faire.


Je crois que je vais laisser tomber ce point pour l'instant, mais je note
qu'il a peut-être une possibilité de le faire...
Merci.

Avatar
Yannick Le goc
Frédéric Gourul wrote:
"Yannick Le goc" a écrit dans le message de
news:bu6hil$t83$


Une maniere de le faire(sans doute la seule) est de se baser sur la
specialisation partielle de classe incomplete, mais en fait c'est
complique d'autant que dans ton cas c'est un constructeur qu'il faut
specialiser partiellement, interdit par le standard.



Juste une question, si ca n'avais pas été un constructeur, la spécialisation
partielle aurait été plus simple ? Car, au final, ca ne sera pas au niveau
du constructeur dont j'en aurais besoin, mais dans la méthode qui connecte
la fonction au foncteur.

Et il y a un moyen pour vérifier à la compilation qu'une classe est bien une
dérivée d'une autre ?


constructeur ou methode c'est la meme chose, on ne specialise

partiellement que des classes ou structures. Verifier a la compilation
qu'une classe derive d'une autre peut se faire mais la aussi c'est
difficile. Alexandrescu propose dans "modern c++ design" une maniere de
le faire.

Yannick