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 :

to_string template pour conteneur [c++11/c++14]


Sujet :

Langage C++

  1. #1
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut to_string template pour conteneur [c++11/c++14]
    Bonjour à toutes et à tous


    Je suis en train de me faire une fonction pour convertir en chaîne de caractères les objets de mon api. J'utilise c++14. Mais si vous avez une solution en c++17 ou c++20 n'hésitez pas à la proposer car même avec ces normes je ne vois pas comment faire :p.
    Du coup j'ai des nombres, des pair, des conteneurs (je ne mets pas les tuples pour le moment mais si ça intéresse quelqu'un d'itérer sur un tuple -> https://stackoverflow.com/questions/...dtuple-in-c-11)


    L'idée est d'appeler to_string avec un objet ou un nombre en paramètre et d'avoir en retour leur représentation en chaîne de caractères. La représentation dans l'exemple est un exemple simple pour développer l'algorithme.

    J'ai trouvé une solution pour les conteneurs mais ça m'oblige à définir un template pour chaque type de conteneur.
    J'aurais bien aimé une seule fonction qui chapeaute tout les conteneurs.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
     
    template <typename T_Container>
    std::string to_string(const T_Container& value) 
    {
        std::stringstream ss;  
        ss << "{" ;
     
        for(auto it : value)    
            ss << to_string(*it);
     
        ss << "}";
        return ss.str();
    }

    Le problème c'est qu'elle ne permet pas au compilateur de la différencier de la fonction pour les types simples:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    template <typename T>
    std::string to_string(T value) 
    {
        std::stringstream ss;
        ss.precision(std::numeric_limits<T>::digits10);
        ss << "T: " << value;
        return ss.str();
    }
    Auriez-vous une idée ?

    Un grand merci.

    Voici le code.

    Lien vers coliru pour éditer le code:
    https://coliru.stacked-crooked.com/a/c3e36f81070384ea
    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
     
    #include <iostream>
    #include <sstream>
    #include <limits>
    #include <map>
     
    template <typename T>
    std::string to_string(T value) 
    {
        std::stringstream ss;
        ss.precision(std::numeric_limits<T>::digits10);
        ss << "T: " << value;
        return ss.str();
    }
     
    template <>
    std::string to_string(long value) 
    {
        std::stringstream ss;
        ss.precision(std::numeric_limits<long>::digits10);
        ss << "long: " << value;
        return ss.str();
    }
     
    template <typename T1, typename T2>
    std::string to_string(const std::pair<T1, T2>& value) 
    {
        std::stringstream ss;
        ss << "{" << to_string(value.first) << ", " << to_string(value.second) << "}";
        return ss.str();
    }
     
    /*
    template <typename T_Container>
    std::string to_string(const T_Container& value) 
    {
        std::stringstream ss;  
        ss << "{" ;
        
        for(auto it : value)    
            ss << to_string(*it);
        
        ss << "}";
        return ss.str();
    }
     
    /*/
    template <typename ... Ts>
    std::string to_string(const std::map<Ts...>& value) 
    {
        std::stringstream ss;  
        ss << "{" ;
     
        for(auto it : value)    
            ss << to_string(it);
     
        ss << "}";
        return ss.str();
    }
    //*/
     
    int main()
    {
        long toto_long = 1;
        float toto_float = 2.3f;
        std::pair<long, float> toto_pair = {toto_long, toto_float};
        std::map<long, float> toto_map_less = {{toto_long, toto_float}, {toto_long*2, toto_float}};
        std::map<long, float, std::greater<long>> toto_map_greater = {{toto_long, toto_float}, {toto_long*2, toto_float}};
     
        std::cout << to_string(toto_long) << " " << to_string(toto_float) << std::endl;    
        std::cout << to_string(toto_pair) << std::endl;    
        std::cout << to_string(toto_map_less) << std::endl;
        std::cout << to_string(toto_map_greater) << std::endl;
    }
    Le résultat attendu:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    long: 1 T: 2.3
     
    {long: 1, T: 2.3}
     
    {{long: 1, T: 2.3}{long: 2, T: 2.3}}
     
    {{long: 2, T: 2.3}{long: 1, T: 2.3}}

  2. #2
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Bonjour,

    Il te faut utiliser le SFINAE. Note qu'en C++20 avec les concept, il y une solution encore plus simple.
    On va créer un type de retour qui n'aura aucun sens si la première n'est pas iterable, et pour l'autre qui n'aura aucun sens si on ne peur pas l'utiliser avec stringstream
    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
    // notre type de retour, c'est std::string mais ça dépend des conditions, ici on impose un paramètre qui doit être un type existant
    template<typename> struct string_ { using type=std::string; };
     
    template <typename T_Container>
    typename string_<typename T_Container::const_iterator>::type  // n'a de sens que si on à un const_iterator
    to_string(T_Container const& value) {
        std::stringstream ss;
        ss << "{";
     
        for (auto const& it : value)
            ss << to_string(it);
     
        ss << "}";
        return ss.str();
    }
     
    template <typename T>
    typename string_<decltype(std::stringstream{}<<std::declval<T>())>::type // on doit pouvoir faire ss<<T
    to_string(const T& value) {
        std::stringstream ss;
        ss.precision(std::numeric_limits<T>::digits10);
        ss << "T: " << value;
        return ss.str();
    }
    Tes fonctions seront donc clairement différenciées. Et de plus la première fonctionne sur les collections mais aussi sur des flux.
    Mais si tu veux faire to_string(std::string("hello")) problème. Les 2 sont valides car on peut itérer dans une string et on peut convertir une string en une string. Alors où tu considères que de toute façon ça n'a pas de sens, où tu définis une troisième fonction qui gérera les std::string.

  3. #3
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    Merci beaucoup.

    (Je ne vais pas avoir le temps de tester ça avant samedi soir.)

    Je ne connaissais pas le principe de SFINAE. (Si il y a un tutorial/faq sur developpez je suis preneur. J'ai fait une simple recherche j'ai pas trouvé)
    https://en.cppreference.com/w/cpp/language/sfinae

    Ça reste encore assé confu pour moi. Je vais relire ça plusieurs fois.

    Ce que je comprend pour le moment c'est que les templates que l'on défini ont un typename d'une certaine nature. Ce qui permet de le différencier un template d'un autre ayant la même signature de fonction.
    Du coup ça me résout d'autre problèmes. Comme par exemple une fonction bool IsValidString(const string& str) qui n'avait aucun type pour la différencier. Et avec la SFINAE ça me résoudra sûrement le soucis. Enfin c'est pour le moment ce que je comprend :p.


    J'en profite pour poser d'autres questions :

    1 - C'est nouveau depuis quelques années je vois de plus en plus des paramètres d'entrés qui s'écrivaient (const Toto& value), s'écrire dorénavant (Toto const& value)

    Je ne comprend pas bien la différence. Sinon pourquoi ne pas écrire const Toto const& value et s'assurer que tout soit constant?

    2 - J'ai vue que tu as mis const T&. Je suis curieux de te confronté mon résonnement. (J'en profite désolé)

    Hormis pour le cas de std::string et des conteneurs, je ne vais pas passer par référence mes nombres et pointeurs partagé. Pour les nombres le passage par copie est plus rapide que par référence (du moins c'était vrais il y a 6 ans). Pour mes pointeurs partagés le passage par référence arrive régulièrement à des surprises dans mon api. J'utilise un système d'observation qui peut réagir à une action et la référence sur le pointeur partagé peux ne plus être valide, (car le ref count n'aura pas été incrémenté car passé par référence).

    Du coup le passage par copie s'avère plus rapide pour les nombres et sécurisé pour les pointeurs partagés.

    D'ailleurs je me demandai si un pointeur partagé passé en copie n'est pas optimisé.

    Encore merci pour ta réponse.

  4. #4
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    0-
    Le SFINAE permet d'effacer une fonction ou une classe template, si un paramètre du template ou la valeur de retour ou un paramètre de la fonction qui dépend d'un paramètre de template est incohérent. La fonction ou la classe est alors invisible.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    template<class T, class E=typename T::begin >  class X{};
     
    int main() {
        X<std::vector<int>>  a; // OK
        X<int>  b;              // erreur X<int> n'est pas une classe !
    }
    1-
    Les 2 sont équivalents. On peut écrire const et volatile avant ou après le mot désignant le type. Attention s'il y a plusieurs niveaux il n'y a qu'une seule possibilités pour les autres const. Et constexpr ou mutable doivent être toujours au début.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    const T& x; <=> T const& x; // référence à un T constant
    const T* p; <=> T const* p; // pointeur non constant sur un T constant
    T* const p = nullptr;       // pointeur constant sur un T non constant
    const T* const p = nullptr; <=> T const* const p = nullptr; // pointeur constant sur un T constant
    constexpr T* p = nullptr;   // p est une constante de compilation sur un T non constant
    2-
    Oui le passage par référence est moins performant qu'une copie pour un type simple. Mais dans cas le d'une const référence le compilateur va optimiser et ça revient très souvent au même. J'ai mis une const référence car on n'est pas sûr que le type sera toujours simple ici, donc pour les types simples le compilateur optimisera et pour les types complexes il utilisera la const référence. Pour les template c'est recommandé d'éviter les passages par copie, la const référence est garantie de toujours fonctionner alors que la copie peut poser des problèmes (non optimal voire erreur sur objet non copiable.)

    Et si on passe un pointeur par copie, il n'y a pas d'optimisation la valeur du pointeur est directement copiée, on ne peut pas faire mieux.

  5. #5
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    Un grand merci.

    Le code suivant est possible ?
    ça ne devrait pas être:

  6. #6
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 147
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : Canada

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 147
    Billets dans le blog
    4
    Par défaut
    Ce n'est pas en tatônnant la syntaxe qu'on arrive à un résultat.
    Ça suit une règle très simple
    - const s'applique à ce qui est à sa gauche
    - s'il n'y a rien à gauche, alors il s'applique à ce qui se trouve à sa droite
    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.

  7. #7
    Expert confirmé
    Homme Profil pro
    Ingénieur développement matériel électronique
    Inscrit en
    Décembre 2015
    Messages
    1 599
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 62
    Localisation : France, Bouches du Rhône (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur développement matériel électronique
    Secteur : High Tech - Électronique et micro-électronique

    Informations forums :
    Inscription : Décembre 2015
    Messages : 1 599
    Par défaut
    Citation Envoyé par Suryavarman Voir le message
    Un grand merci.

    Le code suivant est possible ?
    ça ne devrait pas être:
    Les 2 sont valides mais n'indiquent pas la même chose. Pour le détail je l'ai déjà écrit.

  8. #8
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    Pour les const j'irai faire des tests avec le code pour comprendre.

    Sinon pour le sujet principale c'est nickel sa fonctionne et j'ai compris le principe.

    Il me reste une dernière question
    pourquoi construire une stringstream avec les accolades plutôt qu'avec les traditionnelles parenthèses?*Dans ce cas présent tu souhaites gérer des cas particuliers ?

    std::stringstream{} -> std::stringstream()

  9. #9
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut Différencier un tuple et un array avec la technique de substition SFINAE
    Bonjour,

    J'ai bien avancé sur le sujet. Note je suis passé à c++17 pour pouvoir utiliser les std::string_view dans l'analyseur syntaxique. Je ne peux pas pour le moment passer à c++20 (je vais devoir faire un travail sur mes dépendances).

    Serialization.h
    Serialization.tpp (corps des templates)
    Serialization.cpp
    Serialization.ut.cpp (tests unitaires)

    Le code permet de transformer en chaîne de caractères les types simples et les structures standard de c++.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
     
    const auto value = std::pair<std::wstring, std::vector<int>>{L"toto_1", {0, 1, 2, 3}};
     
    std::wcout << API::toString(value) << std::endl;
    Résultat:
    {"toto_1", {0, 1, 2, 3}}
    Le code permet aussi d'analyser une chaîne de caractères et de la convertir vers un type compatible.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
     
    const auto str = LR"({"toto_1", {0, 1, 2, 3}})"s;
    API::Serialization::Parser parser{str};
     
    auto value_0 = parser.GetValue<std::pair<std::wstring, std::vector<int>>>();
     
    std::wcout << value_0.first << " : " << API::toString(value_0.second) << std::endl;
    Résultat:
    toto_1 :{0, 1, 2, 3}

    Actuellement je suis sur la sérialisation des tuples. Hors il est impossible de distinguer un tuple, d'un std::array ou d'une std::pair, avec le concept de SFINAE.

    J'ai essayé ça mais comme prévu le compilateur ne sait pas quel template choisir.
    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
     
        #define MA_SFINAE_TO_STRING_TUPLE \
            typename API::string_<typename std::tuple_element<0, T_Tuple>::type, \
                                  typename std::tuple_element<std::tuple_size_v<T_Tuple>, T_Tuple>::type>::type
     
        #define MA_SFINAE_NODE_TUPLE \
            typename API::nodeptr_<typename std::tuple_element<0, T_Tuple>::type, \
                                   typename std::tuple_element<std::tuple_size_v<T_Tuple>, T_Tuple>::type>::type
     
        template <typename T_Tuple>
        MA_SFINAE_TO_STRING_TUPLE toString(const T_Tuple& value);
     
        template <class T_Tuple>
        T_Tuple FromNode(const MA_SFINAE_NODE_TUPLE& node);
     
        template <class T_Tuple>
        bool IsValidNode(const MA_SFINAE_NODE_TUPLE& node);
     
        template <class T_Tuple>
        API::Serialization::Errors GetErrors(const MA_SFINAE_NODE_TUPLE& node);
     
        namespace Tuple
        {
            /// Permet d'utiliser std::apply et une expression pliée
            template<typename... Ts>
            std::wstring toString(std::tuple<Ts...> const& value);
        }
    Pour le corps des templates de la sérialisation des tuples, j'ai pour le moment implémenté juste la fonction toString.
    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... Ts>
    std::wstring API::Tuple::toString(std::tuple<Ts...> const& value)
    {
        std::wstringstream ss;
        ss << g_BeginKeyWord;
     
        std::apply
        (
            [&ss](Ts const&... tuple_args)
            {
                std::size_t n{0};
                ((ss << API::IsRepresentelByString<decltype(tuple_args)>() ? LR"#(")#" + API::toString(tuple_args) + LR"#(")#" : API::toString(tuple_args)
                     << (++n != sizeof...(Ts) ? g_SeparatorKeyWord + L" " : L"")), ...);
            }, value
        );
     
        ss << g_EndKeyWord;
        return ss.str();
    }
     
    template <typename T_Tuple>
    MA_SFINAE_TO_STRING_TUPLE API::toString(const T_Tuple& value)
    {
        return API::Tuple::toString(value);
    }
    Auriez vous une idée pour différencier les tuples des autres structures en c++17? Si vous avez une solution pour le c++20 je suis aussi preneur.

    Merci d'avance.

  10. #10
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut toString(tuple{"toto", 1, 2.1f}) => {"toto", 1, 2.1}
    J'ai trouvé. Je pensais pas que ça serait possible d'utiliser std::enable_if_t avec la SFINAE. Je peux donc exclure ou inclure des structures. Du coup ça m'a résolu d'autres soucis.
    Mais pour ce qui concerne les tuples voici comment je me suis pris:

    En tête:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
     
        #define MA_SFINAE_TO_STRING_TUPLE \
            typename API::string_<std::enable_if_t<std::is_base_of<decltype(std::tie(std::declval<T_Tuple>())), T_Tuple>::value>>::type
     
        template <typename T_Tuple>
        MA_SFINAE_TO_STRING_TUPLE toString(const T_Tuple& value);
     
        namespace Tuple
        {
            /// Permet d'utiliser std::apply et une expression pliée... ne peut
            /// fonctionner que si API::toString est défini pour les tuples.
            template<typename... Ts>
            std::wstring toString(std::tuple<Ts...> const& value);
        }
    Corps:
    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
     
    template<typename... Ts>
    std::wstring API::Tuple::toString(std::tuple<Ts...> const& value)
    {
        // Je n'utilise pas un stringstream car j'utilise wxWidgets et c'est définition d'opérateur << gâchent tout :p.
        std::wstring str = g_BeginKeyWord;
     
        std::apply([&str](auto&&... tuple_args)
        {
            std::size_t n{0};
            // expression repliée
            ((
              str += (API::IsRepresentedByString<decltype(tuple_args)>() ? LR"#(")#"s + API::toString(tuple_args) + LR"#(")#"s : API::toString(tuple_args))
                 + (++n != sizeof...(Ts) ? std::wstring{g_SeparatorKeyWord + L" "s} : std::wstring{L""s})
              ), ...);
        }, value);
     
        str += g_EndKeyWord;
        return str;
    }
     
     
    template <typename T_Tuple>
    MA_SFINAE_TO_STRING_TUPLE API::toString(const T_Tuple& value)
    {
        return API::Tuple::toString(value);
    }
    Test unitaire avec criterion:
    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
     
    Test(Serialization, tuple_to_string_0)
    {
        typedef std::tuple<std::wstring, int, float> tuple;
     
        const auto tuple_str = LR"({"toto", 1, -2.1})"s;
        const auto tuple_value = tuple{L"toto", 1, -2.1f};
     
        const auto tuple_str_from_value = API::Tuple::toString(tuple_value);
     
        cr_assert_eq(tuple_str, tuple_str_from_value,
                     "(tuple_str: %s) != (tuple_str_from_value: %s)",
                     API::to_string(tuple_str).c_str(),
                     API::to_string(tuple_str_from_value).c_str());
    }
    A noté que j'ai dû spécialiser plus d'une fonction «IsRepesentedByString» pour les string . Car il faut prendre en compte lorsque la string est passée par référence constante ou non etc.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring>();
        template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring&>();
        template<> M_DLL_EXPORT bool IsRepresentedByString<std::wstring&&>();
        template<> M_DLL_EXPORT bool IsRepresentedByString<const std::wstring&>();
        template<> M_DLL_EXPORT bool IsRepresentedByString<const std::wstring&&>();

  11. #11
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    Je me suis trompé. Dans le test unitaire précédemment cité, je n'utilise pas API::toString mais API::Tuple::toString. Du coup il n'y avait aucun problème à la compilation.

    Du coup ma SFINAE ne fonctionne pas.
    L'idée est, que pour savoir si on a affaire à un tuple, il faut reconstruire un tuple à partir des éléments du tuple original et de comparer si le type du nouveau tuple est le même que le tuple d'origine.

    Pour ce faire j'ai ces deux fonctions:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
     
        template<typename T_Tuple, std::size_t... I>
        constexpr auto t2t_impl(const T_Tuple& in_tuple, std::index_sequence<I...>)
        {
            return std::make_tuple(std::get<I>(in_tuple)...);
        }
     
        template<typename T_Tuple, typename Indices = std::make_index_sequence<std::tuple_size_v<T_Tuple>>>
        constexpr auto t2t(const T_Tuple& in_tuple)
        {
            return t2t_impl(in_tuple, Indices{});
        }
    En appelant t2t (tuple to tuple) on créé une séquences du nombre d'éléments défini par T_Tuple (si c'est une std::pair il y aura deux éléments).
    t2t_impl parcour chaque élément du tuple et renvoie le tuple créé.

    Ce qui permet d'avoir le résultat suivant:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
        std::cout << API::TypeName<tuple>() << std::endl;
        std::cout << API::TypeName<decltype(API::t2t(tuple_value))>() << std::endl;
    Résultat:
    T = std::tuple<std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >, int, float>
    T = std::tuple<std::__cxx11::basic_string<wchar_t, std::char_traits<wchar_t>, std::allocator<wchar_t> >, int, float>
    Maintenant je peux utiliser ça pour définir la SFINAE qui laissera passer seulement les tuples.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        #define MA_SFINAE_TO_STRING_TUPLE \
            typename API::string_<std::enable_if_t<std::is_base_of<decltype(t2t(std::declval<T_Tuple>())), T_Tuple>::value>>::type
     
        template <typename T_Tuple>
        MA_SFINAE_TO_STRING_TUPLE toString(const T_Tuple& value);

    Voici l'erreur résultante:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
     
    /usr/include/c++/10/tuple: Dans l'instanciation de «*constexpr const size_t std::tuple_size_v<bool>*»*:
    …/API/Serialization.h:674:81:   requis par la substitution de «*template<class T_Tuple, class Indices> constexpr auto API::t2t(const T_Tuple&) [with T_Tuple = bool; Indices = <missing>]*»
    …/API/Serialization.h:687:5:   requis par la substitution de «*template<class T_Tuple> typename API::string_<typename std::enable_if<std::is_base_of<decltype (API::t2t(declval<T_Tuple>())), T_Tuple>::value, void>::type>::type API::toString(const T_Tuple&) [with T_Tuple = bool]*»
    …/API/include/API/Serialization.h:762:87:   requis depuis ici
    /usr/include/c++/10/tuple:1255:61: erreur: type «*std::tuple_size<bool>*» incomplet utilisé dans un spécificateur de noms imbriqué
     1255 |     inline constexpr size_t tuple_size_v = tuple_size<_Tp>::value;
          |                                                             ^~~~~
    Du coup la SFINAE se transforme en SFIAE.

    Pour réduire le test j'ai réduit la SFINAE à :
    Elle fonctionne mais ne permet pas de distinguer les tuple des
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
     
        #define MA_SFINAE_TO_STRING_TUPLE \
            typename API::string_<typename std::tuple_size<T_Tuple>::value_type>::type
    Le problème venait que std::tuple_size<T_Tuple>::value_type s'écrit aussi std::tuple_size_v<T_Tuple> et même si on ajoute typename à std::tuple_size_v<T_Tuple> cela produit l'erreur:
    erreur: «*tuple_size_v*» dans l'espace de noms «*std*» ne nomme pas un type de patron
    Du coup j'ai modifier t2t:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
     
        template<typename T_Tuple, typename Indices = std::make_index_sequence<typename std::tuple_size<T_Tuple>::value_type>>
        constexpr auto t2t(const T_Tuple& in_tuple)
        {
            return t2t_impl(in_tuple, Indices{});
        }
    Du coup ça fait l'erreur suivante avec l'appel à toString avec un « long unsigned int »:
    erreur: non concordance de type/valeur pour l'argument 1 dans la liste des paramètres du patron de «*template<long unsigned int _Num> using make_index_sequence = std::make_integer_sequence<long unsigned int, _Num>*»
    on attendait une constante de type « long unsigned int », on a obtenu «*typename std::tuple_size<_Tp>::value_type*»

    Du coup je suis bloqué là pour le moment.



    Si cela intéresse quelqu'un voici la fonction API::TypeName:
    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
        /// Une fonction qui peut être utile pour afficher le nom d'un type
        /// quelconque.
        /// \see https://stackoverflow.com/questions/81870/is-it-possible-to-print-a-variables-type-in-standard-c
        /// \example
        /// \code
        /// std::cout << API::TypeName<decltype(value)>() << std::endl;
        /// \endcode
        template <typename T>
        constexpr auto TypeName() noexcept
        {
            std::string_view name = "Error: unsupported compiler", prefix, suffix;
            #ifdef __clang__
                name = __PRETTY_FUNCTION__;
                prefix = "auto type_name() [T = ";
                suffix = "]";
            #elif defined(__GNUC__)
                name = __PRETTY_FUNCTION__;
                prefix = "constexpr auto type_name() [with T = ";
                suffix = "]";
            #elif defined(_MSC_VER)
                name = __FUNCSIG__;
                prefix = "auto __cdecl type_name<";
                suffix = ">(void) noexcept";
            #endif
     
            name.remove_prefix(prefix.size());
            name.remove_suffix(suffix.size());
     
            return name;
        }

  12. #12
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    759
    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 : 759
    Par défaut
    Je n'ai pas comprit quelque chose. Si tu veux différencier std::tuple d'autres tuple-likes, pourquoi ne pas avoir un prototype avec foo(std::tuple<Ts...>) ?

  13. #13
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    Citation Envoyé par jo_link_noir Voir le message
    Je n'ai pas comprit quelque chose. Si tu veux différencier std::tuple d'autres tuple-likes, pourquoi ne pas avoir un prototype avec foo(std::tuple<Ts...>) ?
    Effectivement pour toString ça fonctionne parfaitement. Ouille j'étais vraiment trop barré dans mon truc pour voir ça.

    Mais le problème se reporte sur d'autres fonctions qui sont:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
        template <class T_Tuple>
        T_Tuple FromNode(const MA_SFINAE_NODE_TUPLE& node);
     
        template <class T_Tuple>
        bool IsValidNode(const MA_SFINAE_NODE_TUPLE& node);
     
        template <class T_Tuple>
        API::Serialization::Errors GetErrors(const MA_SFINAE_NODE_TUPLE& node);
    Je vois la chose ainsi:
    Pour ces autres fonctions, j'ai pas un paramètre d'entré pour valider le type. C'est au template de définir la signature du type accepté.

  14. #14
    Membre éclairé Avatar de Suryavarman
    Homme Profil pro
    Développeur 3D
    Inscrit en
    Mai 2006
    Messages
    233
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 41
    Localisation : France, Hérault (Languedoc Roussillon)

    Informations professionnelles :
    Activité : Développeur 3D
    Secteur : Tourisme - Loisirs

    Informations forums :
    Inscription : Mai 2006
    Messages : 233
    Par défaut
    C'est bon j'ai trouvé la SFINAE pour les tuples.

    Voici le code:
    En-tête
    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
     
        template<typename T_Tuple, std::size_t... I>
        constexpr auto t2t_impl(const T_Tuple& in_tuple, std::index_sequence<I...>)
        {
            return std::make_tuple(std::get<I>(in_tuple)...);
        }
     
        template<typename T_Tuple>
        constexpr auto t2t(const T_Tuple& in_tuple)
        {
            auto index_sequence = std::make_index_sequence<std::tuple_size_v<T_Tuple>>();
            return t2t_impl(in_tuple, index_sequence);
        }
     
        /// Le code typename std::tuple_size<T_Tuple>::value_type accepte seulement
        /// les structures de type tuple compatibles comme les std::array. Ce qui
        /// permet d'utilser t2t sans les erreurs de ce genre : erreur: type
        /// « std::tuple_size<bool » incomplet utilisé dans un spécificateur
        /// de noms imbriqué.
        /// Le code std::enable_if_t<std::is_base_of<decltype(t2t(std::declval<T_Tuple>())), T_Tuple>::value>
        /// compose un tuple à partir des types des éléments que compose T_Tuple. Si
        /// le tuple composé est du même type ou que T_Tuple en hérite, alors la
        /// condition est vrais.
        #define MA_SFINAE_NODE_TUPLE \
            typename API::nodeptr_<typename std::tuple_size<T_Tuple>::value_type, std::enable_if_t<std::is_base_of<decltype(t2t(std::declval<T_Tuple>())), T_Tuple>::value>>::type
     
        template <class... Ts>
        std::wstring toString(const std::tuple<Ts...>& value);
     
        template <class T_Tuple>
        T_Tuple FromNode(const MA_SFINAE_NODE_TUPLE& node);
    L'implémentation:
    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
     
    template<typename... Ts>
    std::wstring API::Tuple::toString(std::tuple<Ts...> const& value)
    {
        std::wstring str = g_BeginKeyWord;
     
        std::apply([&str](auto&&... tuple_args)
        {
     
            std::size_t n{0};
            // expression repliée
            ((
              str += (API::IsRepresentedByString<decltype(tuple_args)>() ? LR"#(")#"s + API::toString(tuple_args) + LR"#(")#"s : API::toString(tuple_args))
                 + (++n != sizeof...(Ts) ? std::wstring{g_SeparatorKeyWord + L" "s} : std::wstring{L""s})
              ), ...);
        }, value);
     
        str += g_EndKeyWord;
        return str;
    }
     
    template<typename T_Tuple, std::size_t index>
    void API::Tuple::FromNode(T_Tuple& in_tuple, const API::Serialization::Node::Nodes::const_iterator& begin, const API::Serialization::Node::Nodes::const_iterator& end)
    {
        constexpr size_t tuple_size{std::tuple_size_v<T_Tuple>};
     
        if constexpr (index < tuple_size)
        {
            MA_ASSERT(begin != end,
                      L"begin == end"s,
                      std::invalid_argument);
     
            std::get<index>(in_tuple) = API::FromNode<typename std::tuple_element<index, T_Tuple>::type>(*begin);
     
            if constexpr (index + 1u < tuple_size)
                API::Tuple::FromNode<T_Tuple, index + 1u>(in_tuple, begin + 1u, end);
        }
    }
     
    template <class... Ts>
    std::wstring API::toString(const std::tuple<Ts...>& value)
    {
        return API::Tuple::toString(value);
    }
     
    template <class T_Tuple>
    T_Tuple API::FromNode(const MA_SFINAE_NODE_TUPLE& node)
    {
        MA_ASSERT(node->m_Type == API::Serialization::Node::Struct,
                  L"Le nœud n'est pas une structure.",
                  std::invalid_argument);
     
        T_Tuple result{};
     
        const size_t tuple_size{std::tuple_size_v<T_Tuple>};
     
        MA_ASSERT(node->m_Nodes.size() <= tuple_size,
                  L"Le nœud a plus d'enfants que ne le permet le tuple: " +
                  API::toString(tuple_size),
                  std::invalid_argument);
     
        MA_ASSERT(tuple_size > 0,
                  L"Le tuple doit avoir au moins un paramètre.",
                  std::invalid_argument);
     
        API::Tuple::FromNode<T_Tuple, 0>(result, node->m_Nodes.begin(), node->m_Nodes.end());
     
        return result;
    }
    Deux tests unitaires avec criterion:
    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
     
    Test(Serialization, tuple_to_string_0)
    {
        typedef std::tuple<std::wstring, int, float> tuple;
     
        const auto tuple_str = LR"({"toto", 1, -2.1})"s;
        const auto tuple_value = tuple{L"toto", 1, -2.1f};
     
        const auto tuple_str_from_value = API::toString(tuple_value);
     
        cr_assert_eq(tuple_str, tuple_str_from_value,
                     "(tuple_str: %s) != (tuple_str_from_value: %s)",
                     API::to_string(tuple_str).c_str(),
                     API::to_string(tuple_str_from_value).c_str());
    }
     
    Test(Serialization, tuple_from_string_0)
    {
        typedef std::tuple<std::wstring, int, float> tuple;
     
        const auto tuple_str = LR"({"toto", 1, -2.1})"s;
        const auto tuple_value = tuple{L"toto", 1, -2.1f};
     
        const auto tuple_value_from_str = API::FromString<tuple>(tuple_str);
     
        cr_assert_eq(tuple_value, tuple_value_from_str);
    }

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

Discussions similaires

  1. [VisualStudio 2005 Pro] Template pour Console en C
    Par dorian833 dans le forum Visual C++
    Réponses: 2
    Dernier message: 21/02/2007, 11h27
  2. [Template] Utilisation de template pour l'envoi de mail
    Par eXiaNazaire dans le forum Collection et Stream
    Réponses: 2
    Dernier message: 30/03/2006, 10h28
  3. [XSL] xsl:apply-templates pour les attribus
    Par luta dans le forum XSL/XSLT/XPATH
    Réponses: 4
    Dernier message: 24/02/2006, 16h35

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