Partage
  • Partager sur Facebook
  • Partager sur Twitter

enable_if et std::is_same

Quelques soucis de template

Sujet résolu
    1 avril 2019 à 15:01:39

    Bonjour.

    J'ai un problème pour spécifier des templates en fonctions de différents types d'entiers. En effet, sur dertaines machines certains types d'entiers sont identiques et pas sur d'autres. Par exemple std::time_t est parfois définit comme un typedef sur std::int32_t et parfois pas.

    Mon problème est d'écrire une spécialisation template pour les entiers qui ne fasse pas de définitions en double. Ainsi le code suivant plante, car le template A est définit deux fois pour std::int32_t (car std::time_t ici est un typedef sur std::int32_t).

    template<typename T> struct A;
    template<> struct A<std::int32_t>{...};
    template<> struct A<std::time_t> {...}; //ERREUR


    Je voulais écrire du code avec std::enable_if, et j'ai donc écrit ca

    tempalte<typename T, typename is_enabled=void> struct A;
    
    template<> struct A<std::int32_t>{...};
    
    template<> struct A<
      std::time_t,
      typename std::enable_if<!std::is_same<std::time_t,std::int32_t>::value,int>::type
    >{...}; //ERREUR

    Ce nouveau code plante, en effet rien ne dépend de paramètres templates, ce qui fait que les règles de SFINAE ne sont pas appliquées et qu'on a une vraie erreur.

    ==> Comment faire pour forcer l'utilisation des règles SFINAE lorsque l'on travaille sur des types non templates?
    ==> Si ce n'est jamais possible (ce qui va rendre l'écriture de code multi plateforme basé sur des typedefs et non des types distincts un bel enfer), comment on s'en sort?



    -
    Edité par ledemonboiteux 1 avril 2019 à 15:02:37

    • Partager sur Facebook
    • Partager sur Twitter
      1 avril 2019 à 16:27:49

      As-tu besoin de distinguer time_t des autres types d'entiers si tu le peux? Que fais-tu de différent dans ce cas?

      En général, les spécialisations on ne peut guère les écrire que sur les types builtin: char, short..., long long.

      Cela ne m'empêche pas parfois de viser les intX_t (sur des plateformes précises), mais jouer avec int8_t, int16_t... présente un risque car on n'a pas de garantie quand à leur existence. En plus, long et long long peuvent avoir la même taille et être considérés comme différents par le compilo.

      Dans tous les cas, j'évite de mélanger des spécialisations sur des types entiers aux sémantiques différentes car C et C++ ne les distingue pas: on n'a pas de types opaques malheureusement. Car tu vas avoir les mêmes problèmes avec size_t, ptrdiff_t, le possiblement futur ssize_t, etc.

      -
      Edité par lmghs 1 avril 2019 à 16:28:45

      • Partager sur Facebook
      • Partager sur Twitter
      C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
        1 avril 2019 à 16:53:16

        La plupart du temps les time_t et autres sont tout simplement des alias de type natif (e.g. unsigned long long). Et c'est la même chose pour (u)int(8|16|32|64)_t. Du coup en spécialisant ces derniers tu fournis aussi une implémentation pour time_t :) Donc normalement inutile de spécialiser pour les 250 millions de type d'entier de la bibliothèque.

        -
        Edité par markand 1 avril 2019 à 16:53:53

        • Partager sur Facebook
        • Partager sur Twitter

        git is great because Linus did it, mercurial is better because he didn't.

          1 avril 2019 à 18:32:36

          D'autant plus que, la chose que l'on ignore trop souvent, c'est que les alias de type ne sont réellement effectivement que cela : des alias de type; du moins, au niveau de la "liste interne" des types de données du compilateur.

          Peu importe où, quand et comment, mais il arrive un moment, dans la mécanique interne du compilateur, où tous les alias de type que l'on peut créer sont -- purement et simplement -- "traduits" de manière à représenter le type d'origine.

          Si ce n'était pas le cas, un code aussi simple que (désolé, mais le bouton code, et le bouton HTML foirent honteusement ...)

          #include <iostream>
          #include <type_traits>
          int main(){
              using MyInt = int;
              using OtherInt = int;
              if(std::is_same<MyInt, OtherInt>::value)
                  std::cout<<"C'est le meme type \n";
              else
                  std::cout<<"Ce sont des types differents\n";
             
          }
          produirait une sortie proche de "Ce sont des types differents"...

          Or, ce n'est pas le cas, et, avec Gcc, en tout cas, on obtient bel et bien la sortie "C'est le meme type".

          • Partager sur Facebook
          • Partager sur Twitter
          Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
            2 avril 2019 à 11:08:04

            Merci pour vos réponses.

            Pour std::time_t ça règle bien le problème, mais distinguer ente les typedefs et les types différents est un problème que j'ai souvent lorsque j'écris du code template. Les typedefs sont bien des alias de types, et on doit donc avoir std::is_same qui renvoie true.

            J'ai finit par contourner le problème de std::time_t en suivant le conseil de markland.



            J'ai trouvé la solution suivante, qui résoud mon cas, mais qui n'est pas top pour le passage à l'échelle quand on a plein de types, parce qu'on ne peut pas facilement rajouté un type supporté simplement en rajoutant un template. L'idée est de faire en sorte que le code de enable_if dépende toujours d'un paramètre template afin de forcer le compilateur a utiliser SFINAE


            template<typename T, typename is_enabled=void> struct A;

            //A<int>
            template<typename T, std::enable_if<std::is_same<T,int>,int>::type > struct A{...};

            //A<std::time_t>
            template<typename T, std::enable_if<! std::is_same<T,std::time_t>,int>::type > struct A{...};




            Et j'ai aussi une alternative, qui est mieux parce qu'elle gère tous les entiers d'un coup, mais qui ne permet plus de faire des spécialisations pour des entiers particuliers.

            template<typename T, typename is_enabled=void> struct A;
            template<typename T, std::enable_if<std::is_integral<T>,int>::type > struct A{...};


            (Et désolé pour la mise en forme, mais le bouton code bugge).

            @imghs: Je met ces types dans une base de données. std::time_t représente un timestamp, et doit donc être lié au type TIMESTAMP de la base de données. int représente un entier, et doit donc être lié a INTEGER ou BIGINT. Mais comme en C++ std::time_t est un alias de type, ils sont de toutes facons indistinguables, alors je ne vais pas pouvoir m'en sortir sans créer un type distinct pour les timestamps.

            -
            Edité par ledemonboiteux 2 avril 2019 à 11:10:52

            • Partager sur Facebook
            • Partager sur Twitter
              2 avril 2019 à 14:28:12

              A ce moment là, tu devrais presque chipoter en créant deux structure (toujours pas moyen d'introduire du code, sorry):

              struct Integral{

              int64_t value; // std::time_t est en 64 bits chez moi

              };

              strict Time{

              std::time_t value;

              };

              int main(){

                  if(std;;is_same<Integral, Time>::value)

                      std::cout<<"c'est le même\n";

                   else

                      std::cout<<"ils sont différents \n";

              }

              Ceci étant dit, comme les int font  -- a priori -- 4 bytes et que les std::time_t en font -- a priori toujours -- 8, tu peux envisager de tester la taille du type qui t'intéresse..

              Ce ne sera pas parfait (long ou long long font aussi 8 bytes), mais cela pourra te permettre de faire la différence entre int et long ;)

              std;;enable_if<std::is_integral<T>::value && // on ne veut que les entier, pas les réels, pas les structures

                                   sizof(T) == 4 >::value> // on ne veut que les entier qui font explicitement 4 bytes (32 bits)

              /* ... ailleurs

              std;;enable_if<std::is_integral<T>::value && // on ne veut que les entier, pas les réels, pas les structures

                                   sizof(T) == 8 >::value> // on ne veut que les entier qui font explicitement 8 bytes (64 bits)

              • Partager sur Facebook
              • Partager sur Twitter
              Ce qui se conçoit bien s'énonce clairement. Et les mots pour le dire viennent aisément.Mon nouveau livre : Coder efficacement - Bonnes pratiques et erreurs  à éviter (en C++)Avant de faire ce que tu ne pourras défaire, penses à tout ce que tu ne pourras plus faire une fois que tu l'auras fait
                2 avril 2019 à 17:03:28

                Tu as aussi `enum class my_time : std::time_t {};` Mais après il faut rajouter tous les opérateurs, etc
                • Partager sur Facebook
                • Partager sur Twitter
                C++: Blog|FAQ C++ dvpz|FAQ fclc++|FAQ Comeau|FAQ C++lite|FAQ BS| Bons livres sur le C++| PS: Je ne réponds pas aux questions techniques par MP.
                  5 avril 2019 à 22:34:22

                  Pourquoi tu ne gères pas tes types côté c++ (avec de vrais types c++) et le type de stockage à part ?

                  C'est pour ton wrapper sqlite ? Perso je gère les dates avec un type proxy puis j'associe ce type au type sgbd et au type c++. Je te passerai un lien si tu veux un exemple.

                  Et ça permet au user d'utiliser le type qu'il veut côté c++, un time_t, un std::chrono etc ...

                  • Partager sur Facebook
                  • Partager sur Twitter
                    9 avril 2019 à 16:15:40

                    Vous avez raison, c'est mieux d'utuiliser deux types distincts.
                    ads, si tu as un exemple ss la main je suis preneur. Sinon, je pense avoir compris et je vais m'en sortir

                    Merci a tous pour vos conseils

                    • Partager sur Facebook
                    • Partager sur Twitter
                      9 avril 2019 à 17:20:02

                      En gros tu as le type proxy, le type cpp et le type utilisé pour le stockage par le sgbd.

                      Les fichiers qui concernent les types :

                      https://github.com/ads00/ndb/blob/master/include/ndb/type.hpp
                      Ici tu as la liste des types proxy (ndb_type)

                      https://github.com/ads00/ndb/blob/master/include/ndb/engine/type.hpp

                      https://github.com/ads00/ndb/blob/master/include/ndb/engine/sqlite/type.hpp

                      l.19 la liste des types utilisés par sqlite

                      l.28 on associe les types proxy qui n'existent pas pour le sgbd à un type du sgbd.

                      https://github.com/ads00/ndb/blob/master/include/ndb/type/system.tpp

                      l.13 on associe les types proxy au type cpp (j'ai ajouté un système de scopes mais t'en auras ptet pas besoin)

                      Donc pour tes timestamp, tu aurais comme type :

                      Type proxy : timestamp_

                      Type sqlite : int64 (SQLITE_INTEGER)

                      Type cpp : std::time_t

                      Voila voila.

                      • Partager sur Facebook
                      • Partager sur Twitter

                      enable_if et std::is_same

                      × Après avoir cliqué sur "Répondre" vous serez invité à vous connecter pour que votre message soit publié.
                      × Attention, ce sujet est très ancien. Le déterrer n'est pas forcément approprié. Nous te conseillons de créer un nouveau sujet pour poser ta question.
                      • Editeur
                      • Markdown