IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)
Navigation

Inscrivez-vous gratuitement
pour pouvoir participer, suivre les réponses en temps réel, voter pour les messages, poser vos propres questions et recevoir la newsletter

Langage C++ Discussion :

contourner les fonctionnalités manquantes au C++11 de façon standard


Sujet :

Langage C++

  1. #1
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut contourner les fonctionnalités manquantes au C++11 de façon standard
    Bonjour,
    Je suis tombé hier sur un problème qui me turlupine depuis.

    Voyez-vous, j'ai dans mon code besoin de passer une lambda prenant en paramètre un type inconnu à la compilation, mais qui ait comme caractéristique d'être un flux sur lequel divers operator>> sont disponibles.

    M'étant renseigné, j'ai appris qu'il s'agissait là des lambdas polymorphiques, qui apparemment n'existent pas en C++11, pour une obscure raison de concepts, tout aussi inexistants également.

    J'ai essayé de résoudre ce problème (parce que oui, faut bien avancer) avec du type-erasure. Mal m'en a pris ! Je suis tombé fatalement sur une autre fonctionnalité manquante du C++ : les templates virtuels.

    Ne pouvant les utiliser, j'en suis réduit au code suivant, pas du tout flexible puisqu'il faut le modifier pour chaque type de donnée, et une contrainte de plus est que la donnée doit être disponible sur tous les types de flux que j'envisage d'utiliser (pour l'instant sf:: Packet et std::istream, mais rien ne me dis que je ne vais pas en ajouter).

    (je n'ai mis en exemple que la version pour les int)
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    class istr
    {
    private:
        class streamBase
        {
        public:
            streamBase() {};
            virtual ~streamBase() {};
            template <class D>
            void extract(D &d)
            {
                get(d);
            }
        protected:
            virtual void get(int &d) = 0;
        };
     
        template <class S>
        class streamObject: public streamBase
        {
        public:
            streamObject(S &iss): m_iss(iss) {};
            virtual ~streamObject() {};
     
        protected:
            virtual void get(int &d){extract(d);}
     
            template <class D>
            void extract(D &d)
            {
                m_iss >> d;
            }
        private:
            S &m_iss;
        };
     
        std::unique_ptr<streamBase> stream;
     
    public:
        template <class R>
        istr(R &iss): stream(new streamObject<R>(iss)) {}
        virtual ~istr() {};
     
        template <class T>
        inline istr& operator>>(T& var)
        {
            return *this;
        }
    };
    Y-a-t-il des solutions connues pour résoudre ce problème de façon standard au C++11 ? Avez-vous des pistes pour résoudre le problème des templates virtuels ou des lambdas polymorphiques ?

    D'avance merci pour vos réponses.

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 115
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 115
    Points : 32 967
    Points
    32 967
    Billets dans le blog
    4
    Par défaut
    Bonjour,

    pour des lambdas polymorphiques, c'est possible grâce à Boost:: Phenix.
    Pensez à consulter la FAQ ou les cours et tutoriels de la section C++.
    Un peu de programmation réseau ?
    Aucune aide via MP ne sera dispensée. Merci d'utiliser les forums prévus à cet effet.

  3. #3
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Malheureusement je n'ai pas accès à boost, et je ne peux pas changer le type que je dois fournir, c'est obligatoirement une std::function (avec des lambda standard).

  4. #4
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Bonjour,

    Tu as le droit à boost ? Si oui alors boost::phoenix résoudra ton problème de lambda polymorphique.

    L'absence des lambdas polymorphiques est du à un problème de "cohabitation" avec les concepts, or en effet les concepts ont été retiré du C++11, mais ils devraient revenir, et intégrer d'office les lambdas polymorphiques aurait été ajouter des problèmes pour les concepts alors qu'il y en a déjà d'autre à résoudre ...

    Pour les template virtuel, je ne crois pas qu'il soit question que ca existe un jour (quand on réfléchit à la facon dont la compilation se passe, à l'implémentation des tables virtuelles et des instanciations des templates, on se rend compte que ca doit vraiment poser des problèmes d'autoriser des fonctions membres templates virtuelles).

    Edit: A la place du code que tu as écri pour contourner les problèmes, tu pourrais nous donner un exemple de code que tu voudrais écrire à l'utilisation ?

  5. #5
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    Le système en question est un module de ligne de commande.
    Je vous passe les détails de l'implémentation, le code ci-dessous est fonctionnel, mais a le défaut de ne pouvoir utiliser que les std::istream alors que j'aimerais pouvoir utiliser des sf:acket depuis le réseau.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    class LineCommander: public ThreadedModule, sf::NonCopyable
    {
    public:
        LineCommander();
        virtual ~LineCommander();
     
        void registerCommand(const std::string &com, const std::function<void(std::istream&)> &f);
        void unregisterCommand(const std::string &com);
     
        void exec(const std::string &com, std::istream &istr);
    protected:
    private:
        virtual void frame();
     
        std::map<std::string, std::function<void(std::istream&)>> m_coms;
    };
    voilà un exemple d'utilisation de la classe (situé dans un constructeur) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    m_lc.registerCommand("accept", [this](std::istream& iss)
    {
        sf::IpAddress ip;
        unsigned int port(0);
        iss >> ip >> port;
        accept(ip, port);
    });
    avec évidemment le code complémentaire dans le destructeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    m_lc.unregisterCommand("accept");
    EDIT: quelque chose qui résoudrait le problème à la racine serait évidemment une fonction permettant de transformer un sf:: Packet en std::istream, mais ça c'est pas gagné vu que les modèles de buffer n'ont rien de comparable, et que de toute façon ce n'est pas beau.

  6. #6
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Mais en réalité tu connais forcément l'ensemble des types de flux utilisés au moment de la compilation.

    Dans ce cas tu pourrais rajouter en paramètre template de ta classe l'ensemble des types qu'elle utilise et définir un typedef sur un variant (cf boost, c'est pas hors de portée à recoder, AMA), et un opérateur >> qui travail sur ce variant (il va forwarder le bon appel, cf static_visitor).

    Ainsi pour écrire les foncteurs, ton utilisateur utilisateur ton typedef comme type.

  7. #7
    Membre averti Avatar de Nogane
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    241
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 241
    Points : 323
    Points
    323
    Par défaut
    Bonsoir,
    Le variant me semble difficile a coder soit même, a cause de l’opérateur = difficile a rendre exception-safe. Même si c'est vrais que çà peut se tenter.
    En l'absence de lambda polymorphique, la meilleur solution me semble être le type erasure cepandant, plutôt que de wrapper le flux, j'ai penser wrapper la commande. Cela n'est pas aussi concis qu'avec les lambda, mais ça me semble acceptable.
    Je vous donne un exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
    78
    79
    80
    81
    82
    83
    84
    85
    86
    87
    88
    #include <string>
    #include <map>
    #include <iostream>
    #include <sstream>
    #include <memory>
     
    class CmdFunction //! Class abstraite contenant une commande
    {
    	//Non copyable
    	CmdFunction(CmdFunction const&);
    	CmdFunction& operator =(CmdFunction const&);
     
    public:
    	CmdFunction(){}
    	virtual ~CmdFunction() = 0 {};
    };
     
    template<typename S>
    class CmdFunctionForType : public CmdFunction //! Class abstraite contenant est specialisé pour un type de flux
    {
    public:
    	virtual void exec(S& iss) = 0;
    };
     
     
    class LineCommander
    {
    public:
    	void registerCommand(const std::string &com, std::shared_ptr<CmdFunction> handler)
    	{
    		m_coms[com] = handler;
    	}
     
    	void unregisterCommand(const std::string &com)
    	{
    		m_coms.erase(com);
    	}
     
    	template<typename S>
    	void exec(const std::string &com, S &istr)
    	{
    		//! On recupere l'iterateur
    		auto iter = m_coms.find(com);
    		if(iter == m_coms.end())
    			return; //retour ou erreur
     
    		CmdFunction& handler = *iter->second;
    		//! On downcast dans le type template CmdFunctionForType aproprié
    		//! Si ca echoue, c'est que c'est le mauvais type de flux.
    		//! Ca lancera un bad_cast.
    		CmdFunctionForType<S>& downcastedHandler = 
    			dynamic_cast<CmdFunctionForType<S>&>(handler);
    		downcastedHandler.exec(istr);
    	}
     
    private: 
    	std::map<std::string, std::shared_ptr<CmdFunction> > m_coms;
    };
     
     
    int main()
    {
    	LineCommander m_lc;
     
    	//! Specialisation pour le comportement voulue.
    	//! Ok c'est un peut plus compliqué qu'un lambda, mais ca va encore.
    	//! Remarque, on peut passer des argument dans le constructeur, équivalent a 
    	//! la liste de capture dans les lambda.
    	class LocalCmdFunctionForIstream : public CmdFunctionForType<std::istream>
    	{
    	public:
    		void exec(std::istream& iss)
    		{
    			unsigned int ip = 0;
    			unsigned int port = 0;
    			iss >> ip;
    			iss >> port;
    			//accept(ip, port);
    			std::cout << ip << " " << port << std::endl;
    		}
    	};
    	m_lc.registerCommand("accept", std::make_shared<LocalCmdFunctionForIstream>());
     
    	std::stringstream iss("859212563 48496");
    	m_lc.exec<std::istream>("accept", iss); //Attention, ici, il faut specifier le bon type
     
    	return EXIT_SUCCESS;
    }
    Voila, c'est juste un exemple hein, mais ça vous donnera peut-être une piste.

  8. #8
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    @Nogane: Oui mais non, si j'ai bien compris le besoin de l'op, il veut pouvoir exécuter sa commande sur n'importe quel type de flux (hors de toute hiérarchie). Dans ton code, la commande LocalCmdFunctionForIstream ne fonctionne que pour un seul type de flux (si on considère que les flux héritant de std::istream, alors pas besoin de tout ça, le polymorphisme fait son boulot).

    L'idée de wrapper de commande ça correspond à la définition de foncteur. Or dans le cas de l'op, il a besoin de foncteur polymorphique, on peut les réaliser assez facilement à la main ou en utilisant les lambda polymorphiques, mais dans les deux cas les stocker est problématique (j'ai essayé avant de répondre tout à l'heure, et j'ai pas trouvé de solution sur le moment).

  9. #9
    Membre averti Avatar de Nogane
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    241
    Détails du profil
    Informations personnelles :
    Âge : 43
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 241
    Points : 323
    Points
    323
    Par défaut
    @Flob90: Ah oui tu as raison. Je n'avais pas vu les chose comme ça.
    Comme je ne me décourage pas, je propose une variante un peu moins jolie, mais toujours viable(je crois).
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    //! Specialisation pour le comportement voulue.
    template<typename S>
    class SpecificCmdFunction : public CmdFunctionForType<S>
    {
    public:
    	void exec(S& iss)
    	{
    		unsigned int ip = 0;
    		unsigned int port = 0;
    		iss >> ip;
    		iss >> port;
    		//accept(ip, port);
    		std::cout << ip << " " << port << std::endl;
    	}
    };
     
    int main()
    {
    	LineCommander m_lc;
     
    	//Il faut enregistrer tout les types de flux potentiel. 
    	//  (On y couperas pas puisqu'il faut bien les compiler)
    	//On doit pouvoir faire un fonction qui les enregistre tous d'un coup. 
    	//  (Peut-etre en utilisant une type-list)
    	m_lc.registerCommand("accept", std::make_shared<SpecificCmdFunction<std::istream> >());
    	m_lc.registerCommand("accept", std::make_shared<SpecificCmdFunction<sf::Packet> >());
     
    	std::stringstream iss("859212563 48496");
    	m_lc.exec<std::istream>("accept", iss);
     
    	sf::Packet packet;
    	packet << "859212563 48496";
    	m_lc.exec<sf::Packet>("accept", packet);
     
    	return EXIT_SUCCESS;
    }
    L'astuce est d'enregistrer chaque type de flux dans le LineCommander. C'est un peu embêtant mais de toute façon, faudra bien lister les type a un moment.

  10. #10
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    @Nogane: Il manque un bout important dans ton exemple, comment exec va déterminer quel CmdFunction utiliser selon le type du flux ? Au passage c'est redondant d'expliciter le type pour l'appel d'exec, il doit pouvoir le déterminer seul . Cette détermination est typiquement le genre de chose résolu par un visiteur, au final on va retrouver plus ou moins les techniques misent en oeuvre dans boost::static_visitor (ton CmdFunction jouant le rôle d'un boost::variant). Je pense donc que nos deux solutions se rapprochent

  11. #11
    En attente de confirmation mail

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 33
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Points : 3 311
    Points
    3 311
    Par défaut
    Avec boost::variant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33
    34
    35
    36
    37
    38
    39
    40
    41
    42
    43
    44
    45
    46
    47
    48
    49
    50
    51
    52
    53
    54
    55
    56
    57
    58
    59
    60
    61
    62
    63
    64
    65
    66
    67
    68
    69
    70
    71
    72
    73
    74
    75
    76
    77
     
    #include<functional>
    #include<iostream>
    #include<istream>
    #include<map>
    #include<string>
     
    #include<boost\variant.hpp>
     
    namespace N
    {
     
    template<class T>
    class Visitor : public boost::static_visitor<>
    {
    	T& t;
     
    public:
    	Visitor(T& t) : t(t) {}
    	template<class S>
    	void operator()(S* s)
    	{ (*s) >> t; }
    };
     
    template<class Variant>
    struct Stream
    {
    	Variant h;
     
    private:
    	Stream();
    	Stream(const Stream&);
    	Stream& operator=(Stream&);
     
    public:
    	template<class S>
    	Stream(S& s) : h(&s) {}
    	template<class T>
    	Stream& operator>>(T& t)
    	{ Visitor<T> v(t); boost::apply_visitor(v,h); return *this; }
    };
     
    template<class Variant>
    struct LineCommander
    {
    	typedef Stream<Variant> Holder;
     
    private:
    	std::map<std::string,std::function<void(Holder&)> > _map;
     
    public:
    	template<class F>
    	void add(const std::string str, F f)
    	{ _map[str] = f; }
    	template<class S>
    	void exec(const std::string& str, S& s)
    	{ Holder h(s); _map[str](h); }
    };
     
    }
     
    typedef 
    	N::LineCommander<boost::variant<std::istream*> >
    	LC;
     
    void foo(LC::Holder& h)
    { 
    	int i;
    	h >> i;
    }
     
    int main()
    {
    	LC lc;
    	lc.add("a",foo);
    	lc.exec("a",std::cin);
    }
    (testé et compilé sous VC++ 2010 et gcc 4.6)

    La partie qui est à changer c'est :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    typedef 
    	N::LineCommander<boost::variant<std::istream*> >
    	LC;
    Après recoder boost::variant c'est à la base un type erasure, tu devrais pouvoir le recoder (en partie du moins : juste les mécanismes qui te concerne), et passer directement par des pointeurs et pas des valeurs (vu que tu utilises des flux).

  12. #12
    Membre expert

    Avatar de germinolegrand
    Homme Profil pro
    Développeur de jeux vidéo
    Inscrit en
    Octobre 2010
    Messages
    738
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Puy de Dôme (Auvergne)

    Informations professionnelles :
    Activité : Développeur de jeux vidéo
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Octobre 2010
    Messages : 738
    Points : 3 892
    Points
    3 892
    Par défaut
    ça compile ! (me suis débrouillé pour rapatrier la partie de boost qui est utile)

    merci beaucoup
    La seule chose que j'ai réussie à faire de mon côté, c'est ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    template <class M, class N, class ... args>
    struct getPosFromEnd
    {
        const static std::size_t I = sizeof...(args);
        const static bool same = std::is_same<M, N>::value;
        const static std::size_t pos = same*I + (!same)*getPosFromEnd<M, args...>::pos;
    };
     
    template <class M, class N>
    struct getPosFromEnd<M, N>
    {
        const static bool same = std::is_same<M, N>::value;
        const static std::size_t pos = 0;
    };
     
    template <class M, class ... args>
    inline std::size_t getPos()
    {
        return sizeof...(args) - 1 - getPosFromEnd<M, args...>::pos;
    }
    au moins je connais bien les variadics maintenant xD tout comme les type_traits...

+ Répondre à la discussion
Cette discussion est résolue.

Discussions similaires

  1. [phpBB] Quelles sont les fonctionnalités d'administration
    Par psychoBob dans le forum EDI, CMS, Outils, Scripts et API
    Réponses: 11
    Dernier message: 22/06/2006, 13h16
  2. Bloquer les fonctionnalités word
    Par chauve59 dans le forum API, COM et SDKs
    Réponses: 5
    Dernier message: 28/04/2006, 19h22
  3. [INSTALL] où trouver les packages manquant ?
    Par cdu dans le forum Oracle
    Réponses: 4
    Dernier message: 01/04/2006, 12h03
  4. Réponses: 7
    Dernier message: 20/02/2006, 16h18
  5. PostGreSQL et les fonctionnalités spatiales
    Par luta dans le forum PostgreSQL
    Réponses: 1
    Dernier message: 27/05/2004, 17h29

Partager

Partager
  • Envoyer la discussion sur Viadeo
  • Envoyer la discussion sur Twitter
  • Envoyer la discussion sur Google
  • Envoyer la discussion sur Facebook
  • Envoyer la discussion sur Digg
  • Envoyer la discussion sur Delicious
  • Envoyer la discussion sur MySpace
  • Envoyer la discussion sur Yahoo