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 :

ostream, opérateur << : problème avec les namespaces ?


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 ostream, opérateur << : problème avec les namespaces ?
    Bonjour,

    Je viens de tomber sur un problème qui me dépasse...
    Jusqu'à présent, j'ai eu souvent l'occasion d'introduire de nouveaux opérateurs de flux vers std::ostream, et ça ne m'a jamais posé de soucis. Mais j'ai eu besoin aujourd'hui, pour déboguer un programme, de sérialiser un std::array<float,4>.

    Pour m'éviter la corvée de tout ré-écrire à chaque fois, je me suis dit que ça ne pourrait pas faire de mal d'écrire un opérateur << template pour gérer tous les cas possibles :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    namespace utils {
        template<class T, size_t N>
        std::ostream& operator << (std::ostream& o, const std::array<T, N>& a)
        {
            // ...
        }
    }
    À ma grande surprise, je n'ai pas été capable de l'utiliser au sein de mon namespace gui (autre que celui dans lequel j'ai défini l'opérateur : utils) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    namespace gui {
        void foo()
        {
            std::array<float,4> a = {{1,2,3,4}};
            std::cout << a << std::endl;
        }
    }
    Le compilateur semble ne pas trouver l'opérateur en question :
    test.cpp: In function ‘void gui::foo()’:
    test.cpp:32:22: error: cannot bind ‘std::ostream {aka std::basic_ostream<char>}’ lvalue to ‘std::basic_ostream<char>&&’
    /usr/include/c++/4.6/ostream:581:5: error: initializing argument 1 of ‘std::basic_ostream<_CharT, _Traits>& std::operator<<(std::basic_ostream<_CharT, _Traits>&&, const _Tp&) [with _CharT = char, _Traits = std::char_traits<char>, _Tp = std::array<float, 4u>]’
    Cela semble provenir d'un conflit avec un autre opérateur << que j'ai écrit au sein du namespace gui pour un type perso. En effet, voici un exemple minimal qui reproduit 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
    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
    #include <iostream>
    #include <array>
     
    namespace utils {
        // L'operateur que je souhaite rajouter
        template<class T, size_t N>
        std::ostream& operator << (std::ostream& o, const std::array<T, N>& a)
        {
            o << "(";
            for (size_t i = 0; i < N; ++i)
            {
                if (i != N-1)
                    o << a[i] << ", ";
                else
                    o << a[i];
            }
            o << ")";
            return o;
        }
    }
     
    // Un namespace quelconque
    namespace gui {
        // Un nouveau type bidon, juste pour introduire un autre operateur <<
        struct color { unsigned int pack; };
     
        // ... que voici
        std::ostream& operator << (std::ostream& o, const color& c)
        {
            return o << c.pack;
        }
     
        // La fonction qui pose problème
        void foo()
        {
            std::array<float, 4> a = {{1, 2, 3, 4}};
            std::cout << a << std::endl;
        }
    }
     
    int main(int argc, char* argv[])
    {
        gui::foo();
        return 0;
    }
    Si on commente le type bidon color et son opérateur << associé, alors ça compile. Sinon, le compilateur ne trouve pas l'opérateur pour le std::array (il me sort l'erreur citée plus haut, qui apparaît également si on commente l'opérateur << pour std::array).
    Si on met l'opérateur << pour std::array dans le namespace global, ça ne change rien.

    Les seules manières que j'ai trouvées pour que le code compile sont donc :
    • ne définir aucun autre opérateur << dans le namespace gui (vraiment louche),
    • placer l'opérateur << pour std::array dans le namespace gui (pénible, il faut le faire pour chaque namespace),
    • appeler explicitement l'opérateur avec son namespace : utils::operator << (std::cout, a) (lourdingue),
    • ou enfin le définir directement dans le namespace std (ce qui permet d'utiliser le Koenig lookup, si je ne m'abuse, mais je n'aime pas trop magouiller avec le namespace std).


    Avez-vous une idée de ce qui cloche ?
    En y réfléchissant, je pense qu'il est normal que le code ne compile pas, puisqu'aucun des arguments de l'opérateur << n'appartient au namespace utils, et donc le Koenig lookup ne peut pas fonctionner. Mais alors pourquoi est-ce que ça compile s'il n'y a aucun autre opérateur de flux dans le namespace gui ?

    Je suis sous linux, et compile avec gcc 4.6.1 (avec l'option -std=c++0x, bien entendu).

  2. #2
    Invité
    Invité(e)
    Par défaut
    Bonjour,


    Le "problème" vient du mode de recherche de la surcharge appropriée en C++ (Koenig Lookup). Tu peux renseigner dessus ici et . gcc a donc exactement le comportement prévu.
    La meilleure solution serait peut-etre de définir une fonction libre print_arrayTu peux aussi definir un type range dans utils (avec les fonctions qui vont bien pour le construire) et faire un fonction générique ( prenant une paire d'itérateur) :
    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
     
    namespace utils
    {
    template<class It>
    class range { /* ... */ };
     
    template<class Cont>
    range< typename Cont::iterator > whole( Cont& c )
    {
        return range< typename Cont::iterator>(c.begin(), c.end());
    }
     
     
    template<class Cont>
    range< typename Cont::const_iterator > whole( Cont const& c )
    {
        return range< typename Cont::const_iterator >(c.begin(), c.end());
    }
     
    template<class It>
    std::ostream& operator<< ( std::ostream& os, range<It> r )
    {
        typedef typename iterator_traits<It>::value_type Val;
     
        It before_last = r.end();
        before_last--;
     
        os << "(";
        std::copy( r.begin(), before_last, std::ostream_iterator<Val>(os, ", "));
        os << *before_last << ")";
        return os;
    }
    }
     
    // utilisation :
    std::cout << utils::whole(mon_array) << ", " << utils::whole( ma_liste );

  3. #3
    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 Joe Dralliam Voir le message
    Le "problème" vient du mode de recherche de la surcharge appropriée en C++ (Koenig Lookup).
    Je pense avoir bien compris ce mécanisme, mais peut être que c'en est une subtilité qui m'échappe. Voilà un exemple plus simple encore de ce qui me chagrine :
    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
    #include <iostream>
    #include <array>
     
    std::ostream& operator << (std::ostream& o, const std::array<float,4>& a);
     
    namespace NS {
     
        struct color;
     
        // 1
        std::ostream& operator << (std::ostream& o, const color& c);
     
        void foo()
        {
            std::array<float, 4> a = {{1, 2, 3, 4}};
            std::cout << a << std::endl;
        }
    }
     
    int main(int argc, char* argv[])
    {
        NS::foo();
        return 0;
    }
    Ici, l'opérateur que je cherche à appeler est dans le namespace global : je dois donc toujours être capable de l'utiliser, Koenig lookup ou pas. Problème : ça ne compile que si on commente la ligne // 1. Pourquoi ?

    Sinon, je pense que ton approche est satisfaisante. Mais j'aimerais comprendre pourquoi le code ci-dessus ne compile pas

  4. #4
    Invité
    Invité(e)
    Par défaut
    Je viens de retrouver le lien que je cherchais: la section "Name hiding in nested namespace" devrait particulièrement t'interesser. http://www.gotw.ca/publications/mill08.htm

  5. #5
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    La recherche des fonctions en C++ ne prend pas en compte le fait qu'elle soit valide : le compilateur recherche déjà les fonctions dont le nom correspond, et ensuite il regarde celle qui convient le mieux.

    Dans ton dernier exemple, si tu ne commentes pas il y a deux mécanismes de recherche :
    -> Le premier part du scope du lieux de l'appel et remonte jusqu'à trouver une fonction, ici il s'arrete au début et il trouve ta fonction //1 dans le namespace NS.
    -> Le second recherche dans les namespace des arguments de l'appel, ici c'est std : il ajoute l'ensemble des fonction operator << à la liste des fonctions.

    Ensuite il regarde celles qui correspondent : il n'en trouve aucune.

    Lorsque tu commentes, les même méchanismes ont lieux, sauf que le premier méchanisme ne trouve rien dans NS donc il remonte dans le scope global et là il trouve la fonction que tu veux. Et au final cette fonction convient donc ca compile.

    Edit: Tu peux utiliser une directive using pour réimporter les symboles dont tu as besoin dans le namespace que tu veux.

  6. #6
    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
    Ah bien vu ! Merci pour vos réponses.
    Effectivement, en remplaçant l'opérateur par une fonction plus classique (comme dans le dernier lien de Joe), on comprend mieux ce qui se passe.

    C'est assez malvenu, je trouve... Le compilateur devrait être capable de trouver une définition qui compile, si elle existe, et ne pas s'arrêter "bêtement" à la première fonction qui a le bon nom (une fonction c'est un identifier, mais aussi et surtout une signature). Qu'est-ce qui justifie ce comportement (qui, a priori, est inscrit dans la norme) ? Ce n'est pas expliqué dans le billet de Herb Sutter.

    Quand au using, oui c'est une possibilité mais ça reste lourd à utiliser.
    Est-ce une si mauvaise idée d'ajouter cet opérateur "à la main" dans le namespace std ? C'est la solution la plus simple à mettre en place, et qui permet d'utiliser la syntaxe la plus simple, mais est-ce portable/sûr ?

  7. #7
    Membre Expert

    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Août 2004
    Messages
    1 391
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 35
    Localisation : France, Doubs (Franche Comté)

    Informations professionnelles :
    Activité : Ingénieur développement logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 1 391
    Par défaut
    Ca ne me choque pas plus que ca. Quand tu écris un namespace, si tu y met une fonction avec un certain nom, alors ce nom définie une fonctionnalité (deux fonctions avec le même nom définissent la même fonctionnalité : sur des types différents, mais ca reste la même). Ainsi si on appel cette fonction au sein de ce namespace il y a une certaine logique à considérer que c'est cette fonctionnalité qui doit être utilisé et pas un d'un namespace englobant.

    Par contre si cette fonctionnalité n'existe pas au sein du namespace, voir de proche en proche si elle existe reste logique : on ne définit pas cette fonctionnalité mais peut-être que les scope englobant le font, par contre si elle existe, c'est qu'on a prévu une telle fonctionnalité, en utiliser une dans scope englobant revient à utiliser une fonctionnalité non prévue.

    A ca l'ADL vient se rajouter. Comme l'explique Sutter (j'ai pas relu l'article, donc il est possible que j'extrapole un peu), quand on définie une classe, l'ensemble des fonctions du même namespace sont des fonctionnalités associé à la classe. Ainsi ajouter les namespaces des arguments permet de prendre en compte ces diverses fonctionnalités quelque soit le lieux de l'appel.

    Si tu objectif n'est pas de changer les fonctionnalités, mais d'en rajouter une, dans ce cas une directive using convient :
    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
     
    namespace A
    {
    void foo(T);
     
    namespace B
    {
    void foo(U);
    }
    }
    //Ou
    namespace A
    { void foo(T); }
     
    namespace B
    { void foo(U); }
    Dans ce code les deux fonctions sont associé à la même fonctionnalité, cependant on considère que cette fonctionnalité fonctionne différement depuis le scope B que depuis le scope A. Si par contre on considère dans le premier cas que la fonctionnalité présente dans B doit étendre celle de A et non la changer, alors tu peux utiliser une directive using :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    namespace A
    {
    void foo(T);
     
    namespace B
    {
    using A::foo;
    void foo(U);
    }
    }
    De même dans le second code, si tu considères que le scope B doit aussi fournir les fonctionnalités du scope A, alors à nouveau une directive using convient :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
     
    namespace A
    { void foo(T); }
     
    namespace B
    { 
    using A::foo;
    void foo(U); 
    }
    AMA, la première utilisation est plus légitime que la seconde. La première pourrait même dans un sens être le comportement du C++ par défaut (mais dans ce cas il faudrait un moyen de faire "l'inverse" d'un using, je pense que c'est mieux dans ce sens que dans l'autre).

    La seconde (et c'est ton cas) est révélateur d'un ajout d'une fonctionnalité après conception. Dans ton cas tu ajoutes une fonctionnalité à std::array après que la fite classe ai été concu. Dans ce cas le using permet de dire : "J'ai besoin d'utiliser ces fonctionnalités supplémentaire non prévu". Si elles avaient été prévu elle seraient trouvées par l'ADL.

    En ce qui concerne le namespace stl, tu n'as pas le droit d'y ajouter des choses (interdit par la norme), la seul chose que tu peux faire, il me semble, c'est spécialiser les templates.

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

Discussions similaires

  1. [2.x] Problème avec les namespaces !
    Par Guerr dans le forum Symfony
    Réponses: 10
    Dernier message: 27/09/2011, 15h01
  2. [JAXB] JAXB 1.0 Problème avec les namespaces
    Par ekremyilmaz dans le forum Format d'échange (XML, JSON...)
    Réponses: 0
    Dernier message: 30/05/2011, 18h04
  3. [DOM] Problème avec les namespaces
    Par clincks dans le forum Format d'échange (XML, JSON...)
    Réponses: 1
    Dernier message: 26/06/2006, 20h40
  4. Problème avec les opérateurs
    Par jules_lenoir dans le forum Langage
    Réponses: 4
    Dernier message: 26/01/2006, 16h56
  5. []Problème avec les formulaires Outlook
    Par Invité dans le forum Outlook
    Réponses: 6
    Dernier message: 05/12/2002, 09h59

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