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 :

[Généricité] Template design


Sujet :

Langage C++

  1. #1
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut [Généricité] Template design
    Bonjour,

    Depuis ce post, j'ai fait quelques pas.

    J'ai quelques questions sur le design d'une bibliothèque générique de validation et calcul de clé de contrôle.

    Il y a deux actions pouvant être effectuée sur une séquence : calculer une clé de contrôle, et la valider. La seule chose qui change c'est que pour calculer la clé de contrôle il faut faire comme si elle était là, donc le compteur "saute" la position de la clé.

    Pour l'utilisateur lambda, un appel de fonction comme ceci :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    std::string visa_card_number = "412566...";
    std::cout << validate_checksum<visa>(visa_card_number);
     
    std::string visa_without_checkdigit = "4125...";
    std::cout << compute_checkdigit<visa>(visa_without_checkdigit);
    "visa" étant une structure encapsulant tous les détails.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<typename processing,
          typename checkdigit,
          std::size_t size_expected,
    /* ... */
    > struct features;
     
    typedef features< visa_processor, 
                  checkdigit<0, 1>, // position et taille de la clé de contrôle.
                  VISA_SIZE,
    /*...*/
    > visa;
    processing est un foncteur qui sera instancié plus tard et passé à std::accumulate (même si ce n'est pas le cas, c'est l'idée).

    processing doit connaitre la position ! La position est stockée dans processing sous forme d'un foncteur qui a chaque appel (position() ) renvoie la position actuel.

    Mais dans features, processing ne connait pas le type du foncteur de position... Je veux initialiser processing avec celui-ci dans le constructeur.

    Encore un autre problème, un programmeur désire étendre la bibliothèque et crée un foncteur processing mais qui ne demande pas la position... Malheureusement, dans la mécanique interne on initialise avec la position !

    Autre chose, est-ce que c'est bien de rassembler toutes les caractéristiques dans le même template ? C'est pour avoir quelque chose de plus facile à manipuler pour l'utilisateur lambda...

    J'aurais encore d'autres questions mais elles viendront après, je pense que c'est déjà pas mal

    Merci d'avance

  2. #2
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Mon post a eu un succès effrayant et donc je vais quand même mettre la solution que j'ai trouvé.

    Dans un premier temps j'ai utilisé :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<template<class> class processing,
          typename checkdigit,
          std::size_t size_expected,
    /* ... */
    > struct features;
    C'est la première fois que j'ai à utiliser un template de template donc je ne savais pas trop à quoi ça servait et comment/où l'utiliser.

    Ensuite je voulais pouvoir que l'utilisateur puisse initialiser processing avec la position ou avec "rien" si il n'avait pas besoin de la position. La solution que j'ai trouvé c'est :

    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 <template <class> class Function>
    struct function_initializer
    {
      template <typename T>
      static Function<T> init(T &arg)
      {
        return Function<T>(arg);
      }
    };
     
    template <class Function>
    struct function_initialization_empty
    {
      template <typename T>
      static Function init(T &arg)
      {
        return Function(); // arg ignoré
      }
    };
     
    // Exemple
    template<typename processing,
          typename checkdigit,
          std::size_t size_expected,
    /* ... */
    > struct features;
     
    typedef features< position_initializer<visa_processor>, 
                  checkdigit<0, 1>, // position et taille de la clé de contrôle.
                  VISA_SIZE, 
    /*...*/
    > visa;
    Par contre j'aurais bien aimé faire un truc comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    template <typename Function>
    struct initializer
    {
    };
     
    template <typename Function>
    struct initializer<template <class> class Function>
    {
     
    };
    ou encore

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template <size_t argc, typename Function>
    struct initializer
    {
    };
     
    template <typename Function>
    struct initializer<1, template <class> class Function>
    {};
     
    template <typename Function>
    struct initializer<2, template <class, class> class Function>
    {};
    mais apparement ce n'est pas possible.

    En plus j'ai l'impression que ce que j'ai décrit au dessus (avec function_initializer) existe déjà quelque part mais générique pour n argument.

    Finalement, est-ce que c'est mieux d'avoir un appel comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    validate_checksum<visa>(visa_card_number);
    ou comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    checksum<validate_visa>(visa_card_number);
    D'après vous ?

    Merci.

    PS : Si il y a des choses qui vous choquent ou peuvent être faites plus facilement, je suis à l'écoute

  3. #3
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Salut,

    L'impression que cela me donne est une très grande complexité parce que 1/on ne lève pas la tête du guidon et 2/l'"interface" utilisateur porte la complexité de l'implémentation au lieu de la masquer. Quel sont les points de variations intéressants de la bibliothèque ?

    De prime abord, une interface simple comme
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    bool check_visa(std::string visa_code_);
    bool check_pin(std::string pin_);
    // ....
    ou comme :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    template<class input_iterator>
    bool check_visa(input_iterator begin, input_iterator end);
     
    template<class input_iterator>
    bool check_pin(input_iterator begin, input_iterator end);
    // ....
    Tu peux renforcer le typage et écrire des choses comme ça :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    visa v = "12345";
    pin p = "123456";
    check(v); // => correspond à un algo spécifique visa
    check(p); // => correspond à un algo spécifique pin
    check("12345"); // => erreur
    v = p; // => erreur

  4. #4
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Alors les points de variation sont :
    • Le sens du parcours.
    • Vérification de la taille de la séquence.
    • Le corps de l'algorithme (parcours de la séquence en calculant un checksum).
    • La terminaison de l'algorithme, prédicat qui valide le checksum ou fonction qui calcule la clé.
    • Le compteur:
      Si c'est pour le calcul de la clé alors le compteur skip la position "virtuelle" de la clé.
      Si c'est pour la vérification alors le compteur est "normal".
    • Un pré-traitement, càd transformer les caractères en digit ('1' vers 1) et skipper les caractères inutiles (par exemple les '-').


    Pour tes esquisses de code, c'est quelque chose comme ça que j'ai pour le moment (check_visa, compute_visa, check_isbn, ...). Mais j'utilise les range au lieu des iterators (bien que je pourrais proposer un overload sur les deux). Et je n'aime pas renforcer le typage de cette façon, ça bride trop l'utilisation.

    Merci de tes réponses.

  5. #5
    Rédacteur
    Avatar de 3DArchi
    Profil pro
    Inscrit en
    Juin 2008
    Messages
    7 634
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Juin 2008
    Messages : 7 634
    Par défaut
    Citation Envoyé par Trademark Voir le message
    Alors les points de variation sont :
    • Le sens du parcours.
    • Vérification de la taille de la séquence.
    • Le corps de l'algorithme (parcours de la séquence en calculant un checksum).
    • La terminaison de l'algorithme, prédicat qui valide le checksum ou fonction qui calcule la clé.
    • Le compteur:
      Si c'est pour le calcul de la clé alors le compteur skip la position "virtuelle" de la clé.
      Si c'est pour la vérification alors le compteur est "normal".
    • Un pré-traitement, càd transformer les caractères en digit ('1' vers 1) et skipper les caractères inutiles (par exemple les '-').
    Ca me paraît beaucoup de points de variation pour une seule et même fonction. Est-ce que ta fonction n'aurait pas trop de responsabilité ?

    Reprennons :
    Citation Envoyé par Trademark Voir le message
    • Le sens du parcours.
    En quoi laisser la resonsabilité à l'appelant d'utiliser un rbegin et rend (ou l'équivalent pour un range) n'est pas suffisant ? Cela n'est alors plus un point de variation de ta fonction.

    Citation Envoyé par Trademark Voir le message
    • Vérification de la taille de la séquence.
    Pourquoi ne serait-ce pas une fonction à part. La taille correcte étant alors une précondition de la fonction de vérification.

    Citation Envoyé par Trademark Voir le message
    • Le corps de l'algorithme (parcours de la séquence en calculant un checksum).
    Alors là, me vient la question inverse : que reste-t-il dans ta fonction ?

    Citation Envoyé par Trademark Voir le message
    • La terminaison de l'algorithme, prédicat qui valide le checksum ou fonction qui calcule la clé.
    Ok.

    Citation Envoyé par Trademark Voir le message
    • Le compteur:
      Si c'est pour le calcul de la clé alors le compteur skip la position "virtuelle" de la clé.
      Si c'est pour la vérification alors le compteur est "normal".
    • Un pré-traitement, càd transformer les caractères en digit ('1' vers 1) et skipper les caractères inutiles (par exemple les '-').
    Idem : pourquoi ne pas avoir deux fonctions différentes puisqu'il y a deux objectifs différents : un pour le calcul et un pour le check. Cela n'empêche pas qu'elles s'appuient toutes deux ensuite sur des briques communes. Mais au moins ça clarifie la responsabilité de chacune d'entre elles et par là probablement tes idées et ton design

  6. #6
    Membre Expert Avatar de Trademark
    Profil pro
    Inscrit en
    Février 2009
    Messages
    762
    Détails du profil
    Informations personnelles :
    Localisation : France

    Informations forums :
    Inscription : Février 2009
    Messages : 762
    Par défaut
    Merci pour tes réponses, je vais expliquer le pourquoi des choix précédent en fonction de tes réactions.

    Citation Envoyé par 3DArchi Voir le message
    En quoi laisser la resonsabilité à l'appelant d'utiliser un rbegin et rend (ou l'équivalent pour un range) n'est pas suffisant ? Cela n'est alors plus un point de variation de ta fonction.
    Parce que l'utilisateur n'est pas censé savoir dans quel sens l'algorithme impose le parcours de la séquence. De droite à gauche ou de gauche à droite peut tout changer, et laisser ce choix à l'utilisateur n'est peut-être pas très judicieux.


    Citation Envoyé par 3DArchi Voir le message
    Pourquoi ne serait-ce pas une fonction à part. La taille correcte étant alors une précondition de la fonction de vérification.
    Idem qu'au dessus, certaines séquences doivent avoir une taille fixe (par exemple un ISBN-13 à une taille de 13 caractères).

    Citation Envoyé par 3DArchi Voir le message
    Alors là, me vient la question inverse : que reste-t-il dans ta fonction ?
    Je gère les détails restants, incrémenter la taille et initiliaser la fonction "process" avec. Ainsi que vérifier la taille à la fin (si besoin).

    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
    template <typename algorithm,
              template <class> class processor,
              std::size_t size_expected,
              typename seq_iterator,
              typename counter_iter>
    std::size_t compute_checksum(seq_iterator seq_begin, seq_iterator seq_end, counter_iter &counter)
    {
      typedef typename processor<deref<counter_iter> > counter_processor;
      counter_processor process = counter_processor(deref<counter_iter>(counter));
     
      std::size_t checksum = 0;
      for(; seq_begin != seq_end && *counter < size_expected; ++seq_begin, ++counter)
        checksum = process(checksum, *seq_begin);
     
      if(*counter != size_expected || seq_begin != seq_end)
        return bad_sequence;
      return checksum;
    }

    Citation Envoyé par 3DArchi Voir le message
    Idem : pourquoi ne pas avoir deux fonctions différentes puisqu'il y a deux objectifs différents : un pour le calcul et un pour le check. Cela n'empêche pas qu'elles s'appuient toutes deux ensuite sur des briques communes. Mais au moins ça clarifie la responsabilité de chacune d'entre elles et par là probablement tes idées et ton design
    Ok mais la fonction que j'ai posté au dessus est la même pour les deux, est-ce mal ? Sinon il y a quand même une séparation des deux fonctions à un "plus haut niveau".

Discussions similaires

  1. Généricité : template / typedef
    Par PilloBuenaGente dans le forum Débuter
    Réponses: 7
    Dernier message: 17/06/2013, 20h59
  2. Template de Design Pattern
    Par youkoun dans le forum BOUML
    Réponses: 10
    Dernier message: 05/05/2008, 16h54
  3. Design template Creloaded
    Par ptityop dans le forum Autres
    Réponses: 0
    Dernier message: 14/12/2007, 15h37
  4. Réponses: 1
    Dernier message: 10/01/2007, 21h52

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