Affichage des résultats du sondage: Seriez-vous d'accord pour l'ajout d'une telle fonctionnalité au langage C++ ?

Votants
3. Vous ne pouvez pas participer à ce sondage.
  • Oui.

    0 0%
  • Non.

    3 100,00%
  • Je ne sais pas.

    0 0%
  1. #1
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 437
    Points : 1 898
    Points
    1 898

    Par défaut Proposition : équivalent de using namespace pour les fonctions statiques des classes

    Bonjour,

    Soit le code suivant :
    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
    #ifndef INCLUDED__MY_PROJECT__MATH__H
    #define INCLUDED__MY_PROJECT__MATH__H
     
    #include <cassert>
     
    namespace MyProject {
     
    	class Math {
    	public:
    		static double sqrt(double x) {
    			assert(x >= 0);
    			const double result = sqrtImpl(x);
    			assert(result >= 0);
    			return result;
    		}
    	private:
    		static double sqrtImpl(double x);
    	};
     
    }
     
    #endif
    Dans mon exemple, les assert servent à vérifier la précondition et la postcondition, et aussi à les documenter, d'où leur présence dans "Math.h". Le détail de l'implémentation se trouve dans la définition de sqrtImpl, dans "Math.cpp".

    Dans certains bouts de code utilisateur, pour raccourcir l'écriture MyProject::Math::sqrt, j'aimerais pouvoir aller plus loin que using namespace MyProject; et écrire using MyProject::Math::sqrt;, afin de pouvoir écrire directement sqrt.
    De même, dans le cas où je fais appel à beaucoup de fonctions statiques de MyProject::Math, j'aimerais pouvoir écrire quelque chose comme using class MyProject::Math; pour pouvoir utiliser directement ces fonctions sans préfixe.
    Mais le code suivant n'est pas valide en C++ :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    int main()
    {
    	{
    		using class MyProject::Math;
    		std::cout << sqrt(9); // Erreur de compilation
    	}
    	{
    		using MyProject::Math::sqrt; // Erreur de compilation
    		std::cout << sqrt(16);       // Erreur de compilation
    	}
    	return 0;
    }
    Actuellement, en C++, le seul moyen de faire cela est de transformer MyProject::Math en espace de nom :
    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
    #ifndef INCLUDED__MY_PROJECT__MATH__H
    #define INCLUDED__MY_PROJECT__MATH__H
     
    #include <cassert>
     
    namespace MyProject::Math {
     
    	namespace Private {
    		double sqrtImpl(double x);
    	}
     
    	double sqrt(double x) {
    		assert(x >= 0);
    		const double result = Private::sqrtImpl(x);
    		assert(result >= 0);
    		return result;
    	}
     
    }
     
    #endif
    Le problème, c'est que l'on perd la fonctionnalité private. En effet, l'utilisateur peut appeler MyProject::Math::Private::sqrtImpl.

    Au début, j'avais pensé à ajouter au C++ la possibilité d'utiliser private dans un espace de nom :
    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
    #ifndef INCLUDED__MY_PROJECT__MATH__H
    #define INCLUDED__MY_PROJECT__MATH__H
     
    #include <cassert>
     
    namespace MyProject::Math {
     
    private:
    	double sqrtImpl(double x);
     
    public:
    	double sqrt(double x) {
    		assert(x >= 0);
    		const double result = sqrtImpl(x);
    		assert(result >= 0);
    		return result;
    	}
     
    }
     
    #endif
    Mais, contrairement aux classes, les espaces de nom sont extensibles, ce qui permet à l'utilisateur d'écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    namespace MyProject::Math {
    	double triche(double x) {
    		return sqrtImpl(x);
    	}
    }
    et d'appeler directement la fonction triche.

    Du coup, je préfère la possibilité d'avoir l'équivalent de using namespace pour les fonctions statiques des classes.
    Seriez-vous d'accord pour l'ajout d'une telle fonctionnalité au langage C++ ?

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    août 2004
    Messages
    5 364
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 43
    Localisation : France, Yvelines (Île de France)

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

    Informations forums :
    Inscription : août 2004
    Messages : 5 364
    Points : 14 970
    Points
    14 970

    Par défaut

    Je me demande si avec les modules, tu n'as pas déjà ce que tu souhaite avoir. Mais je manque encore de pratique avec eux pour pouvoir être sûr.

    Mais sinon, on est bien d'accord que si tu n'avais pas tes asserts, tu n'aurais aucun problème, tu mettrais ta fonction dans le .cpp. Si c'est le seul use-case que tu as, il est prévu qu'il disparaisse : Avec les contrats, tu spécifies les pré-post conditions sous forme d'attributs, et donc tu peux les mettre avec une simple déclaration de fonction.

    Alors, certes, je te réponds module et contrat, 2 choses qui ne sont pas encore complètement dans le langage, mais comme ta proposition n'y est pas non plus, qu'elle est moins avancée, et qu'elle rencontrerait probablement une forte opposition (certains sont totalement allergiques à tout using visant à simplifier l'usage du scope, et veulent que les gens soient 100% explicites), je pense qu'il y a plus de chances en se tournant vers ces alternatives.
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 517
    Points : 23 005
    Points
    23 005

    Par défaut

    J'ai voté non, non seulement parce que je suis du même avis que JolyLoic au sujet des modules et des contrats, mais surtout parce que je fais justement partie de ces allergiques à l'utilisation de using namespace dont il parlait.

    Quand on se "casse le cul" (excusez mon langage) à créer des espaces de noms, c'est pour une bonne raison: c'est pour créer des "boites" dans lesquelles ranger nos outils afin de les retrouver plus facilement.

    L'utilisation de la directive using namespace a pour résultat de renverser cette boite et de tout étaler sur le bureau. Certains nous dirons qu'ils ont le "bordel organisé", mais cela complique quand même les choses. Surtout si on décide de renverser plusieurs boites distinctes: après il devient difficile de savoir "ce qui vient de où".

    Et les choses seraient encore pis dans ton exemple, du fait que ta fonctions sqrt porte le même nom que deux autres fonctions existantes : l'une dans la "boite" std (que tu pourrait aussi renverser sur ton bureau) au travers de l'inclusion de cmath et une qui est déjà sur ton bureau à la base, au travers de l'inclusion de math.h (l'en-tête C ).

    Dans le meilleur des cas, tu n'aurais qu'un seul en-tête inclus, et le compilateur s'y retrouvera. Mais, dans le pire des cas, tu te retrouves avec les trois fichiers inclus de manière indirecte et avec les deux directives [c]using namespace std;[c] et using classname MyProject::Math (je sais, elle n'existe pas, mais c'est pour faire la différence entre les deux ).

    Et, dans ce pire des cas, même le compilateur n'y retrouvera pas ses jeunes, ce qui t'obligerait de toutes manières à utiliser les noms pleinement qualifiés.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  4. #4
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 437
    Points : 1 898
    Points
    1 898

    Par défaut

    Il est très rare que j'utilise using namespace, mais ça arrive.
    La dernière fois que je l'ai utilisé, c'était à l'intérieur d'une fonction. Un morceau de cette fonction ressemblait à cela :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    	using namespace NombreUtils;
    	const short annee = CheckedConvert<short>(Convertisseur{}.ToInt(anneeStr));
    	const short mois  = CheckedConvert<short>(Convertisseur{}.ToInt(moisStr));
    	const short jour  = CheckedConvert<short>(Convertisseur{}.ToInt(jourStr));
    NombreUtils::CheckedConvert<T, U> prend un entier et le convertit en un autre type d'entier. S'il y a un overflow ou un underflow, une exception est levée avec un message qui précise le type de départ, le type d'arrivée, la valeur de départ et la valeur limite dépassée.
    NombreUtils::Convertisseur est une classe qui convertit des chaînes en nombres ou le contraire. Elle possède plusieurs options dont le choix du séparateur des milliers. Elle utilise le named parameter idiom, d'où l'appel au constructeur.
    using namespace NombreUtils; permet ici de concentrer du code en peu de lignes, sans que l'utilisateur ait besoin de faire du défilement horizontal.

    Plus tard, je me suis dis que le mieux aurait été de factoriser le code dans un template de fonction NombreUtils::Convertisseur::ToInteger<IntegerType>. Le code utilisateur aurait alors été :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    	const short annee = NombreUtils::Convertisseur{}.ToInteger<short>(anneeStr);
    	const short mois  = NombreUtils::Convertisseur{}.ToInteger<short>(moisStr);
    	const short jour  = NombreUtils::Convertisseur{}.ToInteger<short>(jourStr);
    Mais, en attendant d'améliorer le code, le using namespace NombreUtils; dans cette fonction était, à mon sens, une bonne idée.
    Ce n'est quand même pas comme si j'avais balancé un using namespace au début d'un ".cpp", avec une portée qui s'étendait dans tout le fichier.

  5. #5
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 517
    Points : 23 005
    Points
    23 005

    Par défaut

    Mais surtout, tu t'es arrêté à mi chemin. C'est moche, parce que tu avais déjà fait le plus dur!

    En effet, au lieu d'avoir une fonction toInteger, tu aurais pu créer des trait de politiques de conversion selon tes besoins qui aurait été proche de
    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
    namespace NombreUtils{
    /* conversion primitif <-->primitif (cas de base) */
    template <typename Result, Orig>
    struct Converter_trait{
        Result operator()(Orig t ) const{
            return Result{t};
        }
    };
    /* spécialisation partielle pour conversion type primitif --> chaine de caractères
     */
    template <typename Orig>
    struct Converter_trait<std::string, Orig>{
        std::string operator()(Orig t) const{
            return std::to_string(t);
        }
    };
    /* manquent les spécialisations partielles pour conversion chaine de caractères --> primitif */
    et des traits de politique pour la vérification des conversions, proches de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    template <typename T>
    struct Checker_trait{
        static void check(T) const{
            /* je te laisse l'implémenter ;) */
        }
    };
    } // namespace NombreUtils
    Une fois que tu en étais là, tu pouvais assez aisément créer ta classe converter pour qu'elle utilise ces traits de politique, sous une forme proche de
    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
    namespace NombreUtils{
    template <typename Result>
    struct ConverterPolicy{
        template <typename Orig>
        using Converter=  Converter_trait<Result, Orig>;
        using Checker = Checker_trait<Result>;
        /* Et pour la facilité */
        template <typename T>
        static Result convertAndCheck(T t){
            auto result = ToType<T>::operator()(t);
            Checker.check(result);
            return result;
        }
    };
    } // namespace NombreUtils
    Et, une fois que tu en était arrivé là, ilne te restait plus qu'à créer des alias de type (dans n'importe quel espace de noms), par exemple:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    namespace Common{
        using DateConverter =NombreUtils::ConverterPolicy<short>;
        using DateMaker = DateConverter::Converter<std::string>;
        using DateChecker = DateConverter::Checker;
    } // namespace Common
    Et ces alias de type t'aurait permis d'écrire du code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    /* juste la conversion */
    auto annee = DateMaker{}(yearString);
    /* et le check séparé */
    DateChecker ::check(annee);
    /* ou, All-In-One */
    auto annee = DateConverter::convertAndCheck(yearString);
    Comme tu le vois, je n'utilise qu'une seule et unique fois NombreUtils, et cela réduit largement l'intérêt de passer par une directive using namespace (d'autant plus que l'alias de type peux parfaitement se trouver dans un fichier d'en-tête)
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 437
    Points : 1 898
    Points
    1 898

    Par défaut

    Dans le contexte :
    Je veux appeler une fonction déjà existante d'une bibliothèque qui attend des short en entrée : une année, un mois et un jour. C'est elle qui vérifiera si, par exemple, le mois est bien entre 1 et 12.
    De mon côté, j'ai des chaînes que je veux convertir en short. Or, dans le code existant, j'ai déjà une classe qui me permet de convertir des chaînes en int et un template de fonction qui me permet de convertir des int en short en lançant une exception si l'entier en paramètre est plus grand que la valeur maximale de short ou plus petit que la valeur minimale de short.

    Je ne vois pas trop où tu veux en venir avec les classes de politique.

    A part ça, je précise aussi qu'il s'agit de code en entreprise. Or, dans l'entreprise où je travaille, si je vais trop loin dans la programmation par templates, les autres développeurs vont m'accuser d'écrire du code trop compliqué donc impossible à maintenir. Pour eux, les classes de politique, cela va déjà trop loin. Du coup, quand j'écris des templates, c'est avec parcimonie.
    A la maison, par contre, je peux faire ce que je veux.

  7. #7
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 517
    Points : 23 005
    Points
    23 005

    Par défaut

    Citation Envoyé par Pyramidev Voir le message
    Dans le contexte :
    Je ne vois pas trop où tu veux en venir avec les classes de politique.
    En fait, l'idée de base est simple : on respecte le principe de la responsabilité unique presque jusqu'à l'absurde, en faisant en sorte de pouvoir disposer d'objets qui n'exposent qu'un ensemble très restreint de fonctionnalités clairement établie; en faisant en sorte d'obtenir un trait de politique par possibilité envisagée dans un point de variation.

    Mais pour te faire plus facilement comprendre l'idée, je vais te proposer un cas d'étude. Mettons que l'on veuille mettre en place un système de service locator le plus évolutif possible. L'analyse des besoins a déterminé qu'il y avait au moins trois grands points de variation, à savoir que le localisateur peut:
    • accepter au choix un ou plusieurs service(s) à localiser
    • accepter au choix de localiser des services polymorphes ou non
    • accepter l'enregistrement automatique d'un nouveau service "à la demande" (pour autant qu'il soit possible de créer ce service sans avoir à lui fournir de paramètre)

    Chacune de ces possibilités est exclusive par rapport à son homologue: soit le service est polymorphe, soit il ne l'est pas; soit on n'accepte qu'un et un seul service, soit on en accepte plusieurs, etc, mais on peut envisager toutes les combinaisons raisonnables utilisant les trois points de variation.

    Pour mettre en place le point de variation concernant l'arité des éléments maintenus par le service locator, nous allons sans doute créer deux traits de politique distincts à savoir quelque chose comme
    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
     
    template <typename BASETYPE>
    class UniqueHolderTrait{
         /* quelques alias de type, utilisés par facilité */
         using value_type  = BASETYPE;
         using ptr_type = std::unique_ptr<value_type>;
    public:
         /* on peut enregistrer un nouvel élément, qui peut nécessiter des paramètres */
         template <typename REALTYPE, typename ... Args>;
         void registerItem(Args ... args){
             static_assert(std::is_base_of<value_type, REALTYPE>::value, "Bad type requested");
             ptr_.reset(new REALTYPE{args...});
             index_=typeid(REALTYPE);
         }
         /* on peut récupérer l'élément sous-jacent sous forme de référence ou sous forme de référence constante */
         template <typename REALTYPE>
         REALTYPE & get(){
             assert(typeid(REALTYPE) == index_);
             return static_cast<REALTYPE &>(*(ptr_.get());
         }
         template <typename REALTYPE>
         REALTYPE const & getConst(){
             assert(typeid(REALTYPE) == index_);
             return static_cast<REALTYPE const &>(*(ptr_.get());
         }
         template <typename REALTYPE>
         bool exists() const{
             return index_==typeid(REALTYPE) && ptr_.get()!= nullptr;
         }
    private:
        ptr_type ptr_;
        std::type_index index_;
    };
    /* pour maintenir des éléments multiples */
    template <typename BASETYPE>
    class MultipleHolderTrait{
         using value_type  = BASETYPE;
         using ptr_type = std::unique_ptr<value_type>;
         using map_type = std::map<std::type_index, ptr_type;
    public:
         template <typename REALTYPE, typename ... Args>;
         void registerItem(Args ... args){
             static_assert(std::is_base_of<value_type, REALTYPE>::value, "Bad type requested");
             intems[typeid(REALTYPE)].reset(new REALTYPE{args...});
         }
         template <typename REALTYPE>
         REALTYPE & get(){
             assert(items_.find(typeid(REALTYPE) != items_.end() && "Bad type requested");
             return static_cast<REALTYPE &>(*(items_[typeid(REALTYPE)].get());
         }
         template <typename REALTYPE>
         REALTYPE const  & getConst(){
             assert(items_.find(typeid(REALTYPE) != items_.end() && "Bad type requested");
             return static_cast<REALTYPE &>(*(items_[typeid(REALTYPE)].get());
         }
         template <typename REALTYPE>
         bool exists() const{
             auto found =  items_.find(typeid(REALTYPE);
             return found != items_.end() && found->second.get()!= nullptr;
         }
    private:
        map_type items_;
    };
    Tu remarqueras que l'interface de ces deux classes est totalement identique. Il n'y a vraiment guère que leur comportement interne qui change

    Nous pourrons dés lors créer une politique d'arité qui choisira l'un de ces traits en fonction d'un tag proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    struct UniqueItemTag{};
    struct MultipleItemTag{};
    template <typename BASETYPE, typename ARITYTAG = UniqueItemTag>
    class ArityPolicy : public UniqueHolderTrait<BASETYPE>{
     
    };
    /* et une spécialisation partielle pour les éléments multiples: */
     
    template <typename BASETYPE>
    class ArityPolicy<BASETYPE, MultipleItemTag>: public MultipleHolderTrait<BASETYPE>{
     
    };
    Grace à cela, tu pourras aussi bien écrire un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    using MyUniqueHolder = ArityPolicy<MonTypeDeBase>;
    qui créeras un alias de type nommé MyUniqueHolder sur un élément qui ne peut contenir qu'un seul élément à la fois ou un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    using MyMultipleHolder= ArityPolicy<MonTypeDeBase, MultipleItemTag>;
    qui créera un alias de type sur un élément qui peut contenir plusieurs éléments à la fois.

    Et nous ferons pareil (en utilisant les fonctionnalités par le fichier type_traits) pour les deux autres caractéristiques, en créant pour la première des traits et une politique proches de:
    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
     
    template <typename BASETYPE>
    struct PolymorphicTrait{
        template <typename REALTYPE>
         using final_type = REALTYPE;
    };
    template <typename BASETYPE
    struct NonPolymorphicTrait{
        /* il y aura tout un système pour permettre le type erasure ici */
        template <typname REALTYPE>
        using final_type = Derived<REALTYPE>;
    };
    /* par défaut, la politique considère que les éléments sont polymorphes */
    template <typename BASETYPE, bool poly=true>
    class PolymorphicPolicy : {
    public:
        template <typename REALTYPE>
        using final_type = PolymorphicTrait<BASETYPE>::final_type<REALTYPE>;
    };
    template <typename BASETYPE>
    class PolymorphicPolicy<BASETYPE, !std::is_polymorhpic<BASETYPE>::value>{
     
    public:
        template <typename REALTYPE>
        using final_type = NonPolymorphicTrait<BASETYPE>::final_type<REALTYPE>;
    };
    et, pour la deuxième, nous créerons des traits de politiques et une politique qui ressemblerait à quelque chose comme
    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
     
    template <typename BASETYPE>
    struct AutoregistryAllowed{
        template <typename REALTYPE, typename ARITYTAG >
        REALTYPE & get(ArityPolicy<BASETYPE, ARITYTAG > & holder){
            if(! holder.template exists<REALTYPE>())
                holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
                return holder.template get<REALTYPE>();
        }
        template <typename REALTYPE, typename ARITYTAG >
        REALTYPE & getConst(ArityPolicy<BASETYPE, ARITYTAG > & holder){
            if(! holder.template exists<REALTYPE>())
                holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
            return holder.template getConst<REALTYPE>();
        }
    };
    template <typename BASETYPE>
    struct AutoregistryNotAllowed{
        template <typename REALTYPE, typename ARITYTAG >
        REALTYPE & get(ArityPolicy<BASETYPE, ARITYTAG > & holder){
                return holder.template get<REALTYPE>();
        }
        template <typename REALTYPE, typename ARITYTAG >
        REALTYPE & getConst(ArityPolicy<BASETYPE, ARITYTAG > & holder){
            return holder.template getConst<REALTYPE>();
        }
    };
    /* il manque plus que la politique, qui choisira entre ces deux traits en fonction
     * de l'aspect défaut constructible des éléments.
     *
     * Je ne la met pas ici, mais elle serait nommé AutoRegistrationPolicy
     */
    Une fois que tout cela sera fait, nous pourrons utiliser nos trois politiques pour définir notre service locator sous une forme qui serait dés lors proche de
    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
    /* Par défaut, il n'autorise qu'un élément */
    template <typename  BASETYPE, typename ARITYTAG= UniqueItemTag>
    class ServiceLocator{
       using arity_policy = ArityPolicy<BASETYPE, TAG>; // choisi automatiquement le bon trait en fonction de ARITYTAG
       using polymorphic_policy = PolymorphismPolicy<BASETYPE>; // choisi automatiquement le bon trait en fonction de l'aspect polymorphe de BASETYPE
       using autoreg_policy = AutoRegistrationPolicy<BASETYPE>; // choisi automatiquement le bon trait en fonction de l'aspect "défaut constructible" de BASETYPE
    public:
     
         template <typename REALTYPE, typename ... Args>;
         void registerItem(Args ... args){
             holder.template register< PolymorphismPolicy<BASETYPE>::final_type<REALTYPE>>();
         }
     
         template <typename REALTYPE>
         REALTYPE & get(){
             return autoreg_policy.template get<REALTYPE>(holder_);
         }
         template <typename REALTYPE>
         REALTYPE const & get(){
             return autoreg_policy.template getConst<REALTYPE>(holder_);
         }
    private:
        static arity_policy holder_;
    };
    NOTE: Pour une fois, j'ai écrit ce code de tete, sans le tester d'aucune manière (de toutes manières, le principe est plus important que l'implémentation réelle du bastringue). Il n'est donc pas garanti qu'il puisse compiler "en l'état", d'autant plus qu'il manque des pans entiers de l'implémentation
    A part ça, je précise aussi qu'il s'agit de code en entreprise. Or, dans l'entreprise où je travaille, si je vais trop loin dans la programmation par templates, les autres développeurs vont m'accuser d'écrire du code trop compliqué donc impossible à maintenir. Pour eux, les classes de politique, cela va déjà trop loin. Du coup, quand j'écris des templates, c'est avec parcimonie.
    A la maison, par contre, je peux faire ce que je veux.
    Hé bien, ca, c'est bien moche .

    D'autant plus que la technique pousse, en quelques sortes, le SRP dans ses derniers retranchements, et qu'elle permet donc, pour chaque trait de politique, de ne s'inquiéter que de ce qui importe vraiment à un moment donné
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  8. #8
    Membre chevronné
    Avatar de Pyramidev
    Homme Profil pro
    Développeur
    Inscrit en
    avril 2016
    Messages
    437
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Val de Marne (Île de France)

    Informations professionnelles :
    Activité : Développeur

    Informations forums :
    Inscription : avril 2016
    Messages : 437
    Points : 1 898
    Points
    1 898

    Par défaut

    Je connais les classes de politique, mais je ne vois pas où tu veux en venir avec le code de ton message d'hier à 20h42.

    Si j'avais utilisé les classes de politique, je pense que j'aurais fait ainsi :
    • Je documente qu'un type X respecte le concept Convertor<T, U> si :
      -pour chaque instance obj de type X,
      -pour chaque variable u de type UBis tel que std::decay_t<UBis> est égal à U,
      obj.convert<T>(u) doit être une expression valide et convertible en T.
    • Je remplace NombreUtils::CheckedConvert<T, U> par une classe IntegerConvertor qui respecte Convertor<T, U> pour tous types d'entiers T et U. Elle vérifie que la valeur en entrée se trouve bien entre la valeur minimale de T et la valeur maximale de T.
    • Je remplace la version de départ de NombreUtils::Convertisseur par une classe NumberToFromStringConvertor qui respecte Convertor<int, std::string>, Convertor<double, std::string>, Convertor<std::string, int> et Convertor<std::string, double>. Cette classe possède des fonctions publiques qui personnalisent le comportement, par exemple le choix du séparateur des milliers.
    • Je définis un template de classe ConvertorConcac pour concaténer deux convertisseurs. Ainsi, à partir d'un Convertor<T, U> et d'un Convertor<U, V>, je crée une classe qui respecte le concept Convertor<T, V>.
    • Je crée une nouvelle classe NombreUtils::Convertisseur qui encapsule :
      • un NumberToFromStringConvertor pour les conversions de chaîne en double et double en chaîne,
      • un ConvertorConcac<IntegerConvertor, NumberToFromStringConvertor&> pour les conversions de chaîne en entier et
      • un ConvertorConcac<NumberToFromStringConvertor&, IntegerConvertor> pour les conversions d'entier en chaîne.

  9. #9
    Expert éminent sénior
    Avatar de koala01
    Homme Profil pro
    Consultant informatique
    Inscrit en
    octobre 2004
    Messages
    10 517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 45
    Localisation : Belgique

    Informations professionnelles :
    Activité : Consultant informatique

    Informations forums :
    Inscription : octobre 2004
    Messages : 10 517
    Points : 23 005
    Points
    23 005

    Par défaut

    Citation Envoyé par Pyramidev Voir le message
    Je connais les classes de politique, mais je ne vois pas où tu veux en venir avec le code de ton message d'hier à 20h42.
    Ce que je veux dire par là, c'est que ton principal problème vient du fait que ton "élément utilitaire" est une fonction, alors que, si ca avait été un type (un foncteur, dans le cas présent), tu aurais pu utiliser la directive using namespace::class::no_foncteur, voir --vu que tu as quelques template qui trainent (mais le résultat aurait été le même s'il n'y en avait pas eu) -- un alias de type au niveau de ton espace de noms "utilisateur".

    L'alias de type serait d'ailleurs la meilleure idée, car il te permettrait en outre de donner un nom explicite quant au fait que la conversion et la vérification s'effectue... sur des données destinées à représenter des dates.
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

Discussions similaires

  1. Réponses: 15
    Dernier message: 14/03/2008, 09h52
  2. besoin d aide pour les fonction en postgresql
    Par dannw dans le forum PostgreSQL
    Réponses: 5
    Dernier message: 11/07/2007, 21h20
  3. Pourquoi une seule valeur de retour pour les fonctions ?
    Par Bruno75 dans le forum Langages de programmation
    Réponses: 33
    Dernier message: 18/01/2004, 13h58

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