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 :

Templates et traits, constructeur de conversion


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut Templates et traits, constructeur de conversion
    Bonjour à tous,

    Je dispose d'une classe de string template (composée d'un std::basic_string<T>), que j'ai jusqu'alors utilisée comme std::string, c'est à dire avec T = char (on va l'appeler str).
    Cependant, j'ai récemment eu besoin d'utiliser des string unicode, donc T = unsigned int (on va l'appeler ustr).

    Pour l'instant (je simplifie un peu), je n'ai qu'un constructeur :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class T>
    class str_t
    {
    public :
     
        str_t(const T* array);
     
        // ...
    };
    Du coup, si je peux écrire ceci :
    ... ceci n'est pas possible :
    ... car "ma string" est un tableau de char, pas un tableau de uint.
    Il me faudrait écrire :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    ustr s = UTF8ToUnicode("ma string");
    J'aimerai pouvoir ajouter un constructeur à ma classe ustr pour qu'elle soit capable de faire la conversion implicitement.

    Pour cela, je pourrais faire un copier coller de la classe str_t et la spécialiser pour T = uint, mais c'est sacrément moche (str_t est une classe assez énorme)...

    Du coup j'ai eu une idée qui fait intervenir les traits :
    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
    template<class T>
    class str_t
    {
    private :
     
        std::basic_string<T> s;
     
    public :
     
        str_t(const T* array)
        {
            s = array;
        }
     
        str_t(const char* array)
        {
            s = StringTraits<T>::Convert(array);
        }
    };
    Problème si T = char : il y a conflit entre les deux constructeurs.
    Je me suis donc dit que, si le compilateur (gcc) ne pouvait pas compiler le second constructeur (qui est de toute manière inutile pour T = char), alors il "l'oublierait" et se contenterai du premier.

    J'ai donc écrit StringTraits de la sorte :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    template<class T>
    class StringTraits
    {
        // Rien, pour provoquer une erreur de compilation.
    };
    ... et sa spécialisations :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<> class StringTraits<uint>
    {
    public :
     
        std::basic_string<uint> Convert(std::string s);
    };
    A ma grande surprise, le compilateur n'essaye même pas de compiler le second constructeur, et indique toujours qu'il y a un conflit...

    Avez-vous une idée de comment je pourrais m'en sortir ?

  2. #2
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Bon eh bien j'ai trouvé un moyen de contourner le problème :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    template<class T>
    class str_t
    {
    public :
     
        // On rend le constructeur générique
        template<class N>
        str_t(const N* array)
        {
            s = StringTraits<T>::Convert(array);
        }
     
        // ...
    };
    Avec :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    template<class T>
    class StringTraits
    {
    public :
     
        // On implemente seulement la convertion T -> T par défaut
        static std::basic_string<T> Convert(std::basic_string<T> s)
        {
            return s;
        }
    };
    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
     
    template<> class StringTraits<uint>
    {
    public :
     
        // Conversion par défaut
        static std::basic_string<uint> Convert(std::basic_string<uint> s)
        {
            return s;
        }
     
        // Conversion avec char
        static std::basic_string<uint> Convert(std::basic_string<char> s)
        {
            return UTF8ToUnicode(s);
        }
    };

  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,
    Tu peux quand même conserver ton constructeur avec le type de base et éviter une conversion 'vide'. Le constructeur générique n'est instancié que si un autre n'est pas trouvé avant :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class T>
    class str_t
    {
    public :
        // sans conversion 
        str_t(const T* array);
        // avec conversion
        template<class N>
        str_t(const N* array)

  4. #4
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Je ne savais pas, merci !
    Ceci dit, la différence en terme de performance est assez faible non ? Un compilateur pas trop mauvais devrait pouvoir optimiser l'appel à Convert() (ou faut-il que je la déclare inline pour être sûr ?).

    Edit :
    J'ai un nouveau soucis... Je dispose également d'un type perso pour les pointeurs (qui ne fait rien d'autre que de s'initialiser à NULL, mais c'est déjà pas mal), que je vais appeler ptr. Ce type dispose d'un constructeur implicite vers le type de donnée pointée :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    template<class T>
    class ptr
    {
        T* p;
     
    public :
     
        ptr() : p(NULL) {}
     
        ptr(T* value) : p(value) {}
     
    };
    Mon problème :
    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
    void f(const str& s)
    {
    }
     
    void f(ptr<int> p)
    {
    }
     
    int main(int argN, char** args)
    {
        str s = "hello";
        int i = 5;
        ptr<int> p = &i;
     
        f(s); // Ok
        f(p); // Ok
        f(&i); // Erreur
    }
    L'erreur en question :
    error: call of overloaded 'f(int*)' is ambiguous
    note: candidates are: void f(const str&)
    note: void f(ptr<int>)
    Ma classe str ne dispose évidemment pas d'un constructeur implicite pour les int*, mais il reste toujours celui-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            template<class N>
            str_t(const N& mValue)
            {
                s = StringConverter<T>::Construct(mValue);
            }
    ... qui ne compile pas si N = int*, mais le compilateur semble s'en ficher complètement... (si je retire ce constructeur, l'ambiguïté dans le main disparait).

  5. #5
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    J'ai trouvé la parade, avec un genre de boost::enable_if :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            template<class N>
            str_t(const N& mValue, typename StringConverter<T>::template IsDefined<N>::Type* mEnableIf = 0)
            {
                sValue_ = StringConverter<T>::Construct(mValue);
            }
    Puis :
    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<> class StringConverter<uint>
        {
        public :
     
            template<class N, class Dummy = void>
            struct IsDefined
            {
            };
     
            template<class Dummy> struct IsDefined< str_t<char>, Dummy > { typedef bool Type; };
            template<class Dummy> struct IsDefined< std::string, Dummy > { typedef bool Type; };
            template<class Dummy> struct IsDefined< const char*, Dummy > { typedef bool Type; };
            template<class Dummy> struct IsDefined< char*, Dummy >       { typedef bool Type; };
            template<uint N, class Dummy> struct IsDefined< const char[N], Dummy > { typedef bool Type; };
            template<uint N, class Dummy> struct IsDefined< char[N], Dummy >       { typedef bool Type; };
     
            // ...
        };
    La syntaxe est assez horrible, mais c'est totalement transparent pour l'utilisateur, donc ça me va !
    Ca aurait d'ailleurs pu résoudre le problème du post original, maintenant que j'y pense

  6. #6
    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 Kalith Voir le message
    Je ne savais pas, merci !
    Ceci dit, la différence en terme de performance est assez faible non ?
    Question de lisibilité et ... lire la suite

    Citation Envoyé par Kalith Voir le message
    Ma classe str ne dispose évidemment pas d'un constructeur implicite pour les int*, mais il reste toujours celui-ci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
            template<class N>
            str_t(const N& mValue)
            {
                s = StringConverter<T>::Construct(mValue);
            }
    ... qui ne compile pas si N = int*, mais le compilateur semble s'en ficher complètement...
    Et oui, un constructeur semble exister donc les 2 fonctions sont également possibles. Il est vrai que à partit du moment où tu définis un constructeur template, il y a de grandes chances que ce constructeur puisse être valide dans beaucoup d'expression et que le compilateur la teste pour évaluer l'expression y compris à des endroits où tu ne t'y attends pas. C'est pour ça que tu devrais le déclarer comme explicit ... et ne laisser implicit que celui vers le type d'instanciation de ton générique :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    template<class T>
    class str_t
    {
    public :
        // sans conversion 
        str_t(const T* array);
        // avec conversion
        template<class N>
        explicit str_t(const N* array)
    Par conséquent :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::string ch1("implicite");
    std::wstring ch2(L"explicite");
    str_t<char> mes_ch = ch1.c_str(); // implicite
    str_t<char> mes_ch2 = str_t<char>(ch2.c_str()); // nécessite d'appeler explicitement le constructeur
    Je n'ai pas tout regarder ta cuisine sur avec enable_if, mais ça m'a l'air bien compliqué pour quelque chose d'aussi simple qu'une fonction de conversion.

    Du coup, je me souvenais avoir déjà fait quelque chose de ce genre, j'ai fouillé un peu et je me suis rendu compte qu'effectivement, j'avais une différence avec toi : ma conversion demande 2 arguments génériques : un source et un destination. Ce que tu as décliné en plusieurs fonctions dans ta structure. Pourquoi pas. Mais le jour où tu introduis un nouveau type tu es obligé de revenir toucher à toutes tes structures existantes pour ajouter ce nouveau type. En travaillant sur le couple (src;dst) l'introduction d'un nouveau type ne nécessite que l'ajout des nouveaux couples de conversion 'à côté' des existants. Pas besoin de modifier le code déjà écrit (open/close principle) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<class TCharDest, class TCharSource> struct conversion;
    // puis spécialisation pour conversion<T,T>, conversion<char,wchar_t> et conversion<wchar_t,char>

  7. #7
    Membre chevronné

    Homme Profil pro
    Développeur informatique
    Inscrit en
    Juin 2007
    Messages
    373
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 36
    Localisation : Royaume-Uni

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 373
    Par défaut
    Citation Envoyé par 3DArchi Voir le message
    Par conséquent :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    std::string ch1("implicite");
    std::wstring ch2(L"explicite");
    str_t<char> mes_ch = ch1.c_str(); // implicite
    str_t<char> mes_ch2 = str_t<char>(ch2.c_str()); // nécessite d'appeler explicitement le constructeur
    C'est précisément ce que je voulais éviter, cf. mon premier post.
    Je dispose de fonctions libres UTF8ToUnicode() et UnicodeToUTF8() qui pourraient aussi faire l'affaire, mais je voulais que l'utilisation d'un string unicode soit aussi simple que possible, et que je puisse écrire des choses du style :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    ustr s = "  resultat = truc  ";
    if (s.Find("="))
    {
       s.Trim(' ');
       vector<ustr> array = s.Cut("=");
       // ...
    }
    ... plutôt que de devoir encombrer inutilement la synthaxe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    ustr s = UTF8ToUnicode("  resultat = truc  ");
    if (s.Find(UTF8ToUnicode("=")))
    {
       s.Trim(UTF8ToUnicode(' '));
       vector<ustr> array = s.Cut(UTF8ToUnicode("="));
       // ...
    }
    ... alors que tout string/caractère UTF8 est convertible en Unicode (comme float -> double, c'est une promotion de type).

    Citation Envoyé par 3DArchi Voir le message
    Je n'ai pas tout regarder ta cuisine sur avec enable_if, mais ça m'a l'air bien compliqué pour quelque chose d'aussi simple qu'une fonction de conversion.
    Je suis d'accord, mais il faut voir qu'en réalité je dispose de beaucoup d'autres fonctions de conversions (types numériques, booléen, pointeur, ...).

    Je n'ai montré que la surface de mon code ici.
    Enfait, ma classe StringConverter dispose de deux types de fonction :
    • Construct() : appelée par le constructeur template, n'est définie que pour la conversion string -> string.
    • Convert() : appelée par la fonction membre template str_t::Convert(), l'opérateur +, l'opérateur += et l'opérateur << (tous templates eux aussi), est définie pour tous les types convertible en string.


    Citation Envoyé par 3DArchi Voir le message
    En travaillant sur le couple (src;dst) l'introduction d'un nouveau type ne nécessite que l'ajout des nouveaux couples de conversion 'à côté' des existants. Pas besoin de modifier le code déjà écrit (open/close principle) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    template<class TCharDest, class TCharSource> struct conversion;
    // puis spécialisation pour conversion<T,T>, conversion<char,wchar_t> et conversion<wchar_t,char>
    Effectivement. Je vais voir si je peux faire ça de cette manière, merci.

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

Discussions similaires

  1. Réponses: 28
    Dernier message: 12/11/2012, 15h32
  2. Réponses: 2
    Dernier message: 02/05/2011, 21h58
  3. constructeur de conversion
    Par coold dans le forum C++
    Réponses: 9
    Dernier message: 24/05/2008, 13h15
  4. Template - Appelé le constructeur de copy
    Par Erakis dans le forum Langage
    Réponses: 6
    Dernier message: 06/02/2008, 22h42
  5. Constructeur de copie et Template: Transtypage
    Par ikkyu_os dans le forum Langage
    Réponses: 9
    Dernier message: 26/12/2004, 22h29

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