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 :

Utilisation de types utilitaires dans une lib (Vecteurs, Point, etc..)


Sujet :

Langage C++

  1. #1
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut Utilisation de types utilitaires dans une lib (Vecteurs, Point, etc..)
    Hello,

    Je bosse sur une petite bibliothèque type CEGUI, et j'ai besoin d'utiliser des Vector2, Point, Rectangle, ....

    Quelle est la meilleure façon de faire ?
    J'en vois 4, regroupées en 2 approches :
    1a : Fournir une implémentation perso de ces classes, l'utilisateur se démerde pour convertir ses vecteurs en mes vecteurs quand il interagi avec la lib.
    Beaucoup de libs font ça. L'exemple le plus marquant est probablement Qt : on doit convertir ses std::vector en QVector et vice-versa.

    1b : La même chose mais en utilisant une implémentation de Vector2, Point, Rectangle, ... existante : on évite de recoder l'existant, mais le problème de conversion est toujours là.

    2a : On passe les types de vecteurs en paramètre template à la lib et on utilise le pattern adapter. Avec une classe statique.
    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
    template <class T>
    struct Vector2 {
    	Vector2 operator+(Vector2 const& rhs) const { return *this; }
    	Vector2 operator-(Vector2 const& rhs) const { return *this; }
    };
     
    template <class T>
    struct DefaultTypes {
    	typedef Vector2<T> Vec2;
    	typedef Vector2<T> Point;
    };
     
    template <class T>
    struct DefaultAdapter {
    	static T add(T const& lhs, T const& rhs) { return lhs + rhs; }
    	static T sub(T const& lhs, T const& rhs) { return lhs - rhs; }
    	// ...
    };
     
    template <class T=DefaultTypes<float>, template<class> class Adapter=DefaultAdapter>
    struct Foo {
     
    	typedef typename T::Vec2 Vec2;
    	typedef typename T::Point Point;
     
    	virtual ~Foo() {}
     
    	void bar() {
    		Vec2 a, b, c, d;
     
    		// a = b + c
    		a = Adapter<Vec2>::add(b, c);
     
    		//a = b + c + d
    		a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d);
    	}
    };
     
    template <class T=DefaultTypes<float>, template<class> class Adapter=DefaultAdapter>
    struct Bar: Foo<T, Adapter> {
     
    	typedef typename T::Vec2 Vec2;
    	typedef typename T::Point Point;
     
    	void foobar() {
    		Vec2 a, b, c, d;
     
    		// a = b + c
    		a = Adapter<Vec2>::add(b, c);
     
    		//a = b + c + d
    		a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d);
    	}
    };
     
    int main() {
    	Foo<> f;
    	f.bar();
     
    	Bar<> b;
    	b.foobar();
     
    	return 0;
    }
    Avantages :
    Pas de problèmes de conversion.
    Pas de surcout à l'exécution. (je pense ?)

    Inconvénients :
    Je perd l'utilisation des opérateurs dans le code de la lib (Vector2::operator+ devient Adapter<Vector2>::add).
    a = b + c + d est dur à écrire : a = Adapter<Vec2>::add(Adapter<Vec2>::add(b, c), d);.

    2b : La même chose mais en wrappant les vecteurs dans des objets dont je connais l'interface.
    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
    template <class T>
    struct Vector2 {
    	Vector2 operator+(Vector2 const& rhs) const { return *this; }
    	Vector2 operator-(Vector2 const& rhs) const { return *this; }
    };
     
    template <class T>
    struct DefaultTypes {
    	typedef Vector2<T> Vec2;
    	typedef Vector2<T> Point;
    };
     
    template <class T>
    struct DefaultWrapper {
    	typedef T wrapped_type;
    	wrapped_type& ref;
     
    	DefaultWrapper(wrapped_type& ref): ref(ref) { }
     
    	wrapped_type operator+(DefaultWrapper const& rhs) const { return ref + rhs.ref; }
    	// ...
    };
     
    template <template<class> class Wrapper>
    struct allow_w {
    	template <class T>
    	Wrapper<T> w(T& ref) { return Wrapper<T>(ref); }
    };
     
    template <class T=DefaultTypes<float>, template<class> class Wrapper=DefaultWrapper>
    struct Foo: protected allow_w<Wrapper> {
     
    	typedef typename T::Vec2 Vec2;
    	typedef typename T::Point Point;
     
    	virtual ~Foo() {}
     
    	void bar() {
    		Vec2 a, b, c, d;
     
    		// a = b + c
    		a = w(b) + w(c);
     
    		//a = b + c + d
    		a = w(w(b) + w(c)) + w(d);
    	}
    };
     
    template <class T=DefaultTypes<float>, template<class> class Wrapper=DefaultWrapper>
    struct Bar: Foo<T, Wrapper> {
     
    	typedef typename T::Vec2 Vec2;
    	typedef typename T::Point Point;
     
    	void foobar() {
    		Vec2 a, b, c, d;
     
    		// a = b + c
    		a = w(b) + w(c);
     
    		//a = b + c + d
    		a = w(w(b) + w(c)) + w(d);
    	}
    };
     
    int main() {
    	Foo<> f;
    	f.bar();
     
    	Bar<> b;
    	b.foobar();
     
    	return 0;
    }
    Avantages :
    Pas de problèmes de conversion.
    Accès aux opérateurs

    Inconvénients :
    Surcout éventuel à l’exécution (à moins que la création des wrappers ne soit supprimée par optimisation ?)
    a = b + c + d reste dur à écrire, le fait de devoir wrap le résultat intermédiaire n'est pas pratique.

    Dans les cas 2a et 2b, le code doit être fourni, une version pré-compilé ne peut pas être fournie à l'utilisateur mais ça ne pose pas de problème ici. (Peut être possible via type-erasure, au prix d'un surcout supplémentaire ?).

    La version 2a me semble être le meilleur compromis, vous en pensez quoi ? Une meilleure solution existe ?
    (Au passage, 2b semble être un exemple d'un des rares cas d'utilisation de l'héritage protégé *_*).

    J'en profite pour poser une autre question au passage : pourquoi je dois écrire a = Adapter<Vec2>::add(b, c); ?
    La déduction de type ne peut pas être faite car il faut "fixer" une classe avant d'aller chercher la bonne fonction ?

    Est-il préférable d'avoir
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    struct DefaultAdapter {
    	template <class T>
    	static T add(T const& lhs, T const& rhs) { return lhs + rhs; }
    	template <class T>
    	static T sub(T const& lhs, T const& rhs) { return lhs - rhs; }
    	// ...
    };
    Au lieu de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template <class T>
    struct DefaultAdapter {
    	static T add(T const& lhs, T const& rhs) { return lhs + rhs; }
    	static T sub(T const& lhs, T const& rhs) { return lhs - rhs; }
    	// ...
    };
    Si oui, pourquoi ?

  2. #2
    Expert éminent sénior
    Avatar de Médinoc
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Septembre 2005
    Messages
    27 369
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 40
    Localisation : France

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Septembre 2005
    Messages : 27 369
    Points : 41 519
    Points
    41 519
    Par défaut
    Je ne le sens vraiment pas, ce truc:
    Code suspect : Sélectionner tout - Visualiser dans une fenêtre à part
    Vector2 operator+(Vector2 const& rhs) const { return *this; }
    SVP, pas de questions techniques par MP. Surtout si je ne vous ai jamais parlé avant.

    "Aw, come on, who would be so stupid as to insert a cast to make an error go away without actually fixing the error?"
    Apparently everyone.
    -- Raymond Chen.
    Traduction obligatoire: "Oh, voyons, qui serait assez stupide pour mettre un cast pour faire disparaitre un message d'erreur sans vraiment corriger l'erreur?" - Apparemment, tout le monde. -- Raymond Chen.

  3. #3
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Avril 2013
    Messages
    610
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Paris (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : Finance

    Informations forums :
    Inscription : Avril 2013
    Messages : 610
    Points : 1 878
    Points
    1 878
    Billets dans le blog
    21
    Par défaut
    Je vote pour la solution 1b.

    La solution 1a ne résout pas de problème que ne résolve pas 1b et t'oblige à recoder les classes de basiques pour lesquelles il est facile de faire des erreurs (alors CEGUI est un travail déjà très conséquent!). De plus, avec 1b il est possible qu'existent déjà les routines de conversion d'un type dans l'autre.

    La laideur de 2a et 2b est suffisante pour les condamner. Tu pourrais à la rigueur vivre avec un moment, mais si tu t'en écartes un peu et reprends tes esprits, tu n'y reviendras pas.

  4. #4
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @Médinoc: le but était de fournir un exemple minimal mais qui compile, pour l'opérateur + une fonction libre qui fait réellement une addition est effectivement préférable.

    @stendhal666: Il vaut mieux "se lier" à une implémentation particulière et laisser l'utilisateur gérer les conversions que de trimbaler des templates dans tout son code donc ?
    Une solution regroupant le meilleur des 2 mondes n'existe pas ?

  5. #5
    Membre éprouvé
    Profil pro
    Inscrit en
    Juillet 2009
    Messages
    307
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juillet 2009
    Messages : 307
    Points : 983
    Points
    983
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Hello,

    Je bosse sur une petite bibliothèque type CEGUI, et j'ai besoin d'utiliser des Vector2, Point, Rectangle, ....

    Quelle est la meilleure façon de faire ?
    Je pense que la meilleure solution dépend de ce que fait ta bibliothèque.

    Si beaucoup de services et/ou des services complexes alors 1a (ou eventuellement 1b mais souvent ca lie les choses et ca c'est pas bien) me semble bien pour les raisons suivantes :

    - plus simple à comprendre pour l'appelant
    - découplage et possibilité de mettre facilemement en place un mécanisme de log
    - pas de template dans tout ton code
    - si fonction complexe les temps de conversion sont tres souvent negligeables

    Si c'est plus une bibliothèque d'outils légers alors oui la genericité avec des concepts et des templates peut être adaptée mais je te conseille quand meme d'avoir une implementation à toi de ces classes ne serait ce que pour tester ta bibliothèque générique.

  6. #6
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    2c

    Considérer que le contrat et respecté, ne pas faire de conversion et utiliser auto/decltype. En interne utiliser des traits ou des fonctions libres avec une implémentation¹ par défaut pour accéder au membre si le besoin se fait sentir. Les classes sont tellement basiques que quelque soit l'implémentation, les opérateurs de base sont présents.
    Tu peux aussi fournir tes propres classes compatibles avec n'importe quel type qui supporte un contrat.

    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
     
    class Vector2d { ... };
     
    template<class T>
    T & operator += (T & lhs, Vector2d const & rhs)
    {
      x(lhs) += x(rhs);
      y(lhs) += y(rhs);
      return lhs;
    }
     
    template<class T>
    decltype(auto)  x(T & o)
    -> eval_if<is_no_const_ref<decltype(o.x())>>
    { return o.x(); }
     
    template<class T>
    decltype(auto)  x(T & o)
    -> eval_if<!is_ref<decltype(o.x())>, x_wrapper<T>>
    { return {o}; }
     
    template<class T>
    decltype(auto)  x(T const & o)
    { return o.x(); } // ici on peut utiliser un trait
    Avantage:
    - L'utilisateur utilise le type qu'il veut

    Inconvénients :
    - Il faut parfois ajouter du code (fonction libre ou traits) pour rendre compatible les types.
    - Plus compliquer de faire les classes de bases.


    ¹ On peut faire du multi-implémentation. Par exemple, si une classe Rectangle ne dispose pas d'attribut publique nommé x, alors supposer qu'il y a une fonction membre x(). Si x() ne retourne pas une référence, alors retourner un wrapper sur x() et setX() (pour l'opérateur += par exemple).

  7. #7
    r0d
    r0d est déconnecté
    Expert éminent

    Homme Profil pro
    tech lead c++ linux
    Inscrit en
    Août 2004
    Messages
    4 262
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : tech lead c++ linux

    Informations forums :
    Inscription : Août 2004
    Messages : 4 262
    Points : 6 680
    Points
    6 680
    Billets dans le blog
    2
    Par défaut
    Bonjour,

    @Iradrille: je ne comprends pas pourquoi la solution 1 ne te plait pas. Tu parles de conversion, que l'utilisateur devra effectuer des conversions entre ses propres types et ceux de ta lib. Moi il me semble que si le problème de cette conversion se pose, c'est que l'utilisateur a fait une erreur de design. Car lorsqu'on fait bien les choses, les dépendances sont très localisées. Ce que je veux dire par là, c'est qu'on va utiliser une lib (dépendance) à un endroit précis (localisée), mais pas dans tout notre programme. Et quand-bien même: si on a une dépendance sur les services qu'offre ta lib, pourquoi se serait un problème d'utiliser tes types plutôt que "nos propres types"?

    Un exemple concret pour illustrer mon questionnement: si on choisit d'utiliser la SFML pour faire une petite application graphique, on va utiliser les verctor2 et autres rect de la SFML, pourquoi donc utiliser d'autres types s'ils font la même chose?
    « L'effort par lequel toute chose tend à persévérer dans son être n'est rien de plus que l'essence actuelle de cette chose. »
    Spinoza — Éthique III, Proposition VII

  8. #8
    Expert confirmé
    Homme Profil pro
    Étudiant
    Inscrit en
    Juin 2012
    Messages
    1 711
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Étudiant

    Informations forums :
    Inscription : Juin 2012
    Messages : 1 711
    Points : 4 442
    Points
    4 442
    Par défaut
    @renoo, je vais garder ça en tête.
    Mon objectif est de faire une lib "minimale" dans un premier temps pour me lancer dans le dev d'un jeu, et de pouvoir réutiliser ça par la suite.

    @jo_link_noir, fournir un type Vector2 (que ce soit pour pouvoir tester la lib ou dans le cas où l'utilisateur n'a pas de type Vector2) n'est pas un problème, mais si j'utilise ce type perso en interne j'ai toujours le même problème : si l'utilisateur veut récupérer la position ou taille d'un Widget elle sera retourner dans un type perso (et pas dans le type Vector2 du client).

    Fournir l'adaptateur sous forme de fonctions libres peut être intéressant, mais il n'y a à pas de risque de conflits ?
    Fournir un operator+= aussi général me fait un peu peur, mais si ça ne pose pas de problèmes, ça semble être la meilleure solution.
    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
    // client
    template <class T>
    struct Vec2 { };
     
    template <class T>
    Vec2<T>& operator+=(Vec2<T>& lhs, Vec2<T> const& rhs) { return lhs; } // pour avoir une implémentation qui compile
     
    struct Rect { };
    Rect* c_style_op_add_equal(Rect* lhs, Rect const* rhs) { return lhs; } // pour avoir une implémentation qui compile
     
    // lib
    template <class T>
    T& operator+=(T& lhs, T const& rhs); // pas d'implémentation -> "redirige" vers ::operator+=(T&, T const&);
     
    // client
    // on doit fournir une spécialisation pour les types n'ayant pas d'operator+=
    template <>
    Rect& operator+=<Rect>(Rect& lhs, Rect const& rhs) { return *c_style_op_add_equal(&lhs, &rhs); }
     
    // lib, Vec2 et Rect sont fournis via des typedef
    Vec2<int> va, vb;
    va += vb; // appel de ::operator+=(Vec2<int>&, Vec2<int> const&); (ou Vec2<int>::operator+=(Vec2<int> const&) si définie comme fonction membre)
     
    Rect ra, rb;
    ra += rb; // appel de ::operator+=<Rect>(Rect&, Rect const&);
    Citation Envoyé par r0d Voir le message
    Un exemple concret pour illustrer mon questionnement: si on choisit d'utiliser la SFML pour faire une petite application graphique, on va utiliser les verctor2 et autres rect de la SFML, pourquoi donc utiliser d'autres types s'ils font la même chose?
    C'est justement un argument en défaveur des solutions 1a / 1b .

    Si j'utilise des sf::Vector2 partout dans mon code, pourquoi devrais-je utiliser des types différents pour interagir avec la GUI ?

    Pour reprendre ton exemple, imaginons une petite appli graphique utilisant la SFML pour gérer la fenêtre / affichage (une classe Vector2 fournie), j'y ajoute une GUI, si ma lib fourni une classe Vector2 (1a), à un moment ou un autre il y aura des conversions entre les 2 types Vector2.

    Si j'utilise des sf::Vector2 dans ma lib pour éviter les conversions (1b), alors une appli faite avec la SDL (je connais pas trop, mais j'imagine que la SDL fourni une classe Vector2 aussi) aura une dépendante à la SFML juste pour utiliser des sf::Vector2 (et devra se taper des conversions entre Vector2 SDL et sf::Vector2).

    Si j'ai besoin de faire quelques calculs physiques, je vais utiliser une lib pour ça, qui elle aussi fournira probablement une classe Vector2.

    L'idée des solutions 2a/2b (et 2c de jo_link_noir), c'est d'utiliser le type de Vector2 que l'utilisateur utilise car c'est inutile d'avoir plusieurs classes pour ça.

  9. #9
    Responsable 2D/3D/Jeux


    Avatar de LittleWhite
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Mai 2008
    Messages
    26 860
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

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

    Informations forums :
    Inscription : Mai 2008
    Messages : 26 860
    Points : 219 062
    Points
    219 062
    Billets dans le blog
    120
    Par défaut
    Bonjour,
    alors une appli faite avec la SDL (je connais pas trop, mais j'imagine que la SDL fourni une classe Vector2 aussi)
    Déjà, non, pas de classe car la SDL est purement en C.
    On peut penser aux structures et dans ce cas, oui, SDL_Rect -> avec un x,y,width,height. Ce qui risque de ne pas répondre aux besoins simples du Vector2


    C'était juste pour la précision.
    Vous souhaitez participer à la rubrique 2D/3D/Jeux ? Contactez-moi

    Ma page sur DVP
    Mon Portfolio

    Qui connaît l'erreur, connaît la solution.

  10. #10
    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
    Personnellement, je tranche ainsi

    - mini-projet qui utilise SFML ? j'utilise sf::Rect
    - "vrai" projet qui utilisera plusieurs trucs ? j'utilise ma classe Rect et j'écris des converter si besoin
    l'encapsulation aidant, transformer au sein d'une méthode un Rect en sf::Rect etc... est des plus simples, que derrière ce soit SFML ou autre, osef je manipule que mes propres Rect
    les dépendances doivent être minimales, ponctuelles et maitrisées
    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.

  11. #11
    Membre expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    739
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Hérault (Languedoc Roussillon)

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

    Informations forums :
    Inscription : Juin 2011
    Messages : 739
    Points : 3 627
    Points
    3 627
    Par défaut
    Citation Envoyé par Iradrille Voir le message
    Fournir un type Vector2 (que ce soit pour pouvoir tester la lib ou dans le cas où l'utilisateur n'a pas de type Vector2) n'est pas un problème, mais si j'utilise ce type perso en interne j'ai toujours le même problème : si l'utilisateur veut récupérer la position ou taille d'un Widget elle sera retourner dans un type perso (et pas dans le type Vector2 du client).
    Il ne faut pas .
    Si tu choisis quelque chose de full-template, tu peux toujours garder le dernier type utilisais sans jamais le convertir. Si l'utilisateur utilise toujours le même type de vector2d, alors la lib n'utilisera que lui. Si l'utilisateur pour des raisons x ou y utilise 2 types de vector2d, alors la lib utilisera les 2 en essayant de convertir au plus juste si les 2 sont utilisés dans une même opération.
    (Le plus juste pourrais aussi être de laisser l'utilisateur surcharger la fonction, c'est là le gros avantage des fonctions libres ou des traits.)


    Citation Envoyé par Iradrille Voir le message
    Fournir l'adaptateur sous forme de fonctions libres peut être intéressant, mais il n'y a à pas de risque de conflits ?
    Oui et non ^^
    Si le type et la fonction libre sont dans le même namespace, pas de problème.
    S'il n'y a pas de fonction libre dans le namespace, mais qu'une fonction généraliste existe dans un autre namespace (N1), deux choix.
    - La fonction est appelée via une fonction du namespace N1: ok.
    - La fonction est appelée depuis un autre namespace: il faut soit préciser le namespace (peu recommandé pour les évolutions futures), soit exporter la fonction avec using pour ne pas préciser le namespace (pénible à écrire).
    Un petit article si tu ne comprends pas comment faire.

    La meilleure solution à mon avis reste de faire un namespace dédié qui contient using la_fonction;. Comme ça, on peut faire mon_namespace::ma_fonction et toujours appeler la bonne méthode.
    J'aime bien aussi le principe d'objet-fonction qui permet par exemple de faire des algos sans se soucier de l'axe utilisé (l'extérieur indique l'axe à travers l'objet-fonction utilisé).

    (L'exemple utilise swap mais c'est l'idée)
    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 <iostream>
    #include <utility>
    #include <iterator>
    #include <algorithm>
     
    namespace tg {
      namespace adl_barrier {
        using std::swap;
     
        template<class T>
        void swap_impl(T & a, T & b) noexcept(noexcept(swap(a,b))) {
          swap(a,b);
        }
      }
     
      template<class T>
      void swap(T & a, T & b) noexcept(noexcept(adl_barrier::swap_impl)) {
        adl_barrier::swap_impl(a,b);
      }
    }
     
    namespace fn {
      struct swap_fn {
        constexpr swap_fn() noexcept {} // constructeur obligatoire pour faire une instancier constante (clang). Au passage, pourquoi ?
     
        template<class T>
        void operator()(T & a, T & b) const noexcept(noexcept(tg::adl_barrier::swap_impl(a, b)))
        { tg::adl_barrier::swap_impl(a, b); }
      };
     
      template<class T>
      struct static_const
      {
        static constexpr T value{};
        constexpr const T & operator()() const noexcept
        { return value; }
      };
     
      namespace { 
        // référence statique pour assurer l'unicité de la référence entre plusieurs unité de compilation
        constexpr auto const & swap = static_const<swap_fn>::value;
      }
    }
     
    class A {};
    void swap(A &, A&)
    { std::cout << "A\n"; }
     
    namespace N1 {
      class A {};
      void swap(A &, A&)
      { std::cout << "N1::A\n"; }
    }
     
    template<class It1, class It2, class UnaryFn>
    void for_each2(It1 first1, It1 last1, It2 first2, UnaryFn fn)
    {
      for (; first1 != last1; ++first1, ++first2) {
        fn(*first1, *first2);
      }
    }
     
    int main()
    {
      //using tg::swap; // fonctionne aussi
      {
        A a;
        tg::swap(a,a);
      }
      {
        N1::A a;
        tg::swap(a,a);
      }
      {
        int a=1,b=3;
        std::cout << a << b << "->";
        tg::swap(a,b);
        std::cout << a << b << "\n";
      }
     
      {
        int a[] = {1,2,3};
        int b[] = {4,5,6};
        std::cout << a[0] << a[1] << a[2] << b[0] << b[1] << b[2] << "->";
        for_each2(std::begin(a), std::end(a), std::begin(b), fn::swap); //std::range_swap
        std::cout << a[0] << a[1] << a[2] << b[0] << b[1] << b[2] << "\n";
      }
    }

  12. #12
    Membre émérite
    Avatar de white_tentacle
    Profil pro
    Inscrit en
    Novembre 2008
    Messages
    1 505
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Novembre 2008
    Messages : 1 505
    Points : 2 799
    Points
    2 799
    Par défaut
    Citation Envoyé par r0d Voir le message
    Bonjour,

    @Iradrille: je ne comprends pas pourquoi la solution 1 ne te plait pas. Tu parles de conversion, que l'utilisateur devra effectuer des conversions entre ses propres types et ceux de ta lib. Moi il me semble que si le problème de cette conversion se pose, c'est que l'utilisateur a fait une erreur de design. Car lorsqu'on fait bien les choses, les dépendances sont très localisées. Ce que je veux dire par là, c'est qu'on va utiliser une lib (dépendance) à un endroit précis (localisée), mais pas dans tout notre programme. Et quand-bien même: si on a une dépendance sur les services qu'offre ta lib, pourquoi se serait un problème d'utiliser tes types plutôt que "nos propres types"?
    Ou pas.

    Je vais prendre un exemple très simple qui illustre le problème. Lire des données dans une bdd, et les afficher à l’utilisateur après un petit traitement.

    Dans la pire situation, tu vas te retrouver à avoir :
    * un type pour la lecture depuis la base, lié à ta lib de lecture en base
    * un type lié à une lib que tu utilises dans ton petit traitement
    * un type lié à ta lib de gui

    Ça ne concerne pas tes types « métiers », mais plutôt les types « utilitaires », chaînes de caractères, collection, points, etc. Ceux qu’on retrouve partout. Et c’est vraiment ch*ant car au final :
    * tu te tapes des conversions inutiles
    * ça alourdit le code métier
    * ça nuit aux perfs

    Donc pour cette raison, je vote 2c et l’usage de classes de traits.

Discussions similaires

  1. Réponses: 3
    Dernier message: 04/02/2015, 18h19
  2. [OL-2013] Utiliser des blocs de type QuickPart dans une réponse
    Par lodan dans le forum Outlook
    Réponses: 2
    Dernier message: 07/11/2014, 15h42
  3. [MySQL] Utiliser des valeur SQL de type float dans une bdd pour boutique/panier
    Par sybil dans le forum PHP & Base de données
    Réponses: 2
    Dernier message: 05/03/2011, 15h45
  4. Champs de type XML dans une base de données
    Par Flocodoupoil dans le forum Décisions SGBD
    Réponses: 3
    Dernier message: 07/07/2004, 18h57
  5. insertion d'un type date dans une table access
    Par monstour dans le forum ASP
    Réponses: 7
    Dernier message: 18/06/2004, 16h57

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