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 :

ternaire et erreur bizarre


Sujet :

Langage C++

Vue hybride

Message précédent Message précédent   Message suivant Message suivant
  1. #1
    Membre Expert
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    872
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 872
    Par défaut ternaire et erreur bizarre
    Bonjour,

    Ce n'est pas un problème rencontré mais plutôt une curiosité :

    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
    class A
    {
    public:
        A(){}
    };
     
    class B : public A
    {
    public:
        B() : A(){}
    };
     
    class C : public A
    {
    public:
         C() : A(){}
    };
     
    A *test(int i)
    {
        return (i ? (new B) : (new C));
    }
     
    int main()
    {
        test(0);
        return 0;
    }
    Avec MSVC2010 (je précise car avec mingw j'ai autre chose) il me dit :

    main.cpp: erreur : C2446: "pas de conversion de 'B *' en 'A *'. Les types points n'ont aucun rapport entre eux, conversion nécessitant reinterpret_cast, cast de style C ou cast de style fonction"
    Etrange non ? J'avais testé avec un autre compilateur hier et il m'avait sorti une même erreur. Après une petite recherche, les seules solutions que j'avais trouvé étaient de caster. Mais pourquoi ? En temps normal quand on renvoie quelque chose comme ça :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A *test()
    {
      return new B;
    }
    ça fonctionne parfaitement. Bien que le problème n'en soit pas vraiment (il suffirait de faire un if else après tout), je suis curieux de comprendre d'où cela provient.

    Merci d'avance !

  2. #2
    Rédacteur/Modérateur
    Avatar de JolyLoic
    Homme Profil pro
    Développeur informatique
    Inscrit en
    Août 2004
    Messages
    5 463
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 50
    Localisation : France, Yvelines (Île de France)

    Informations professionnelles :
    Activité : Développeur informatique
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Août 2004
    Messages : 5 463
    Par défaut
    J'ai un message d'erreur légèrement différent du tien (pas de mention de A*), et qui correspond exactement à ce à quoi je m'attendais (VC++2012) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
     
    testternarycast\source.cpp(21): error C2446: ':'*: pas de conversion de 'C *' en 'B *'
    Quel est le type de l'expression : i ? (new B) : (new C) ? Tu aimerais qu'elle soit de type A*, mais comment le compilateur le saurait-il ?

    En fait, si le type des deux valeurs possible est le même, pas de soucis, ça devient le type de l'expression. Mais ici, les deux valeurs possibles ont comme type B* et C*, deux types différents. Les règles sont alors plus complexes (voir la norme §5.16), je crois que la version simplifiée est que l'on essaye de caster l'un en l'autre, et réciproquement, afin de trouver un type commun aux deux. Ici, aucune conversion n'existe de l'un à l'autre (il faudrait passer vers un troisième type, A), donc la compilation échoue.

    Le code suivant compile :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A *test(int i)
    {
        return (i ? (static_cast<A*>(new B)) : (new C));
    }
    Ma session aux Microsoft TechDays 2013 : Développer en natif avec C++11.
    Celle des Microsoft TechDays 2014 : Bonnes pratiques pour apprivoiser le C++11 avec Visual C++
    Et celle des Microsoft TechDays 2015 : Visual C++ 2015 : voyage à la découverte d'un nouveau monde
    Je donne des formations au C++ en entreprise, n'hésitez pas à me contacter.

  3. #3
    Membre éclairé
    Homme Profil pro
    Doctorant en Astrophysique
    Inscrit en
    Mars 2009
    Messages
    312
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France

    Informations professionnelles :
    Activité : Doctorant en Astrophysique
    Secteur : Enseignement

    Informations forums :
    Inscription : Mars 2009
    Messages : 312
    Par défaut
    Bonjour.

    Il est nécessaire de caster parce que l'opérateur ternaire ne peut qu'opérer sur des types semblables. En gros, si T et U sont des types, l'opérateur ternaire doit être de la forme :
    bool ? T : T et non bool ? T : U

    Par exemple si tu essayes cela,
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    return (i ? (static_cast<A*>(new B)) : (static_cast<A*>(new C)));
    ça marche

    EDIT:
    @JolyLoic: ah merci, je ne connaissais pas ces règles plus complexes dans le standard, du coup j'avais pris l'habitude de toujours caster manuellement vers le même type.

  4. #4
    Membre Expert
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    872
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 872
    Par défaut
    Je ne pensais pas qu'il faisait cette "vérification" supplémentaire. Je trouve ça légèrement étrange mais si c'est défini dans la norme on n'y peut rien. Je trouve ça juste un peu regrettable...

    Merci pour vos éclaircissements !

  5. #5
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Salut,
    Citation Envoyé par imperio Voir le message
    Je ne pensais pas qu'il faisait cette "vérification" supplémentaire. Je trouve ça légèrement étrange mais si c'est défini dans la norme on n'y peut rien. Je trouve ça juste un peu regrettable...
    En fait, c'est normal...

    Ce qu'il faut savoir, c'est que contrairement à la fonction if(test), l'opérateur ternaire permet de créer des constantes de compilation.

    Pour que cela puisse fonctionner avec les constantes de compilation (donc, en dehors de tout contexte d'exécution dynamique!!!), il faut:
    1. que les types des deux opérandes soient identiques
    2. que le compilateur trouve dans le code toutes les informations qui lui permettront de fournir la constante demandée

    Or, B* (un pointeur sur un objet de type B) et C* (un pointeur sur un objet de type C) sont... deux types absolument différents, et ce, même s'ils ont une base commune.

    Tu remarqueras d'ailleurs que l'on a le même problème avec les classes template!

    Si tu écris une classe template du genre de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    template <typename T>
    struct Trait{
        typedef T * ptr_type; // en C++11 : using ptr_type = T* ;
        void foo( ptr_type value){
            /* ...*/
        }
    };
    les types Trait<A>, Trait<B> et Trait<C> sont trois types totalement différents et indépendants, et ce, même si tu peux effectivement passer un B* ou un C* à la spécialisation Trait<A>::foo.

    Ceci dit, le code exécutable généré par un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    A *test(int i)
    {
        return (i ? (static_cast<A*>(new B)) : (new C));
    }
    a de grandes chances d'être totalement identique au code exécutable généré par un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    A *test(int i)
    {
        if (i)
            return new B;
        return new C;
    }
    voire, même plutôt à un code proche de
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    A  *test(int i)
    {
        A * temp;
        if (i)
            temp = new B;
        else
            temp =new C;
        return temp;
    }
    (Si tant est qu'il y ait effectivement une différence entre les deux, je n'ai pas vérifié au niveau de l'assembleur).

    Dés lors, je sais qu'il y a encore des boites qui préconise la règle de codage connue sous le nom de SESE (Single Entry, Single Exit), et l'on pourrait facilement relancer un débat au sujet de cette règle, mais, toutes choses étant égales, je préfèrerais l'un de ces deux codes (quitte à voir s'il y a bel et bien une différence au niveau de l'assembleur généré) à l'utilisation de l'opérateur ternaire, et ce, d'autant plus si, pour pouvoir utiliser l'opérateur ternaire, il faut passer par un static_cast.

    La raison est à mon sens toute simple:

    Je trouve, personnellement, que même lorsque l'on a l'habitude de l'utiliser, l'opérateur ternaire nécessite un effort de compréhension bien supérieur à celui que peut nécessiter la compréhension d'un simple if (... else) et que si l'on n'a pas le bénéfice d'une optimisation possible (comme c'est le cas dans cette situation), il est sans doute préférable de choisir la forme qui apportera la meilleure visibilité
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

  6. #6
    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
    Cette différence du ternaire par rapport à un if que tu viens d'expérimenter est tout simplement le fait que cette structure conditionnelle a un typage plus fort que la structure plus "classique" du if (en C++ du moins).

    C'est pour moi la seule raison qui fait que j'ai tendance à en utiliser : je sais que je vais faire une structure conditionnel ayant un type bien définie ? Alors j'utilise un ternaire pour que le compilateur m'indique si quelque chose ne va pas.

  7. #7
    Membre Expert
    Avatar de imperio
    Homme Profil pro
    Étudiant
    Inscrit en
    Mai 2010
    Messages
    872
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Ain (Rhône Alpes)

    Informations professionnelles :
    Activité : Étudiant
    Secteur : High Tech - Éditeur de logiciels

    Informations forums :
    Inscription : Mai 2010
    Messages : 872
    Par défaut
    Citation Envoyé par koala01 Voir le message
    Dés lors, je sais qu'il y a encore des boites qui préconise la règle de codage connue sous le nom de SESE (Single Entry, Single Exit), et l'on pourrait facilement relancer un débat au sujet de cette règle, mais, toutes choses étant égales, je préfèrerais l'un de ces deux codes (quitte à voir s'il y a bel et bien une différence au niveau de l'assembleur généré) à l'utilisation de l'opérateur ternaire, et ce, d'autant plus si, pour pouvoir utiliser l'opérateur ternaire, il faut passer par un static_cast.

    La raison est à mon sens toute simple:

    Je trouve, personnellement, que même lorsque l'on a l'habitude de l'utiliser, l'opérateur ternaire nécessite un effort de compréhension bien supérieur à celui que peut nécessiter la compréhension d'un simple if (... else) et que si l'on n'a pas le bénéfice d'une optimisation possible (comme c'est le cas dans cette situation), il est sans doute préférable de choisir la forme qui apportera la meilleure visibilité
    Au debut je faisais des ternaires (en C surtout) car on m'avait dit que c'etait plus rapide a l'execution (je n'ai pas cherche a savoir si c'etait vrai, mais pour le coup je vais le faire car ca fait trop longtemps que cette question est en suspens). Maintenant c'est plutot pour raccourcir un peu mon code et le rendre plus lisible (au moins pour moi). Je prefere clairement ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int func(int a, int b)
    {
      return (a > b ? a : b);
    }
    que :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int func(int a, int b)
    {
      if (a > b)
        return a;
      return b;
    }
    J'ai plus l'habitude de voir le premier cas que le deuxieme donc j'ai fini par m'y faire. Apres chacun son truc.

    Tiens d'ailleurs le ternaire respecte le SESE si je comprends bien. Je ne comprends pas vraiment l'interet de cette regle de codage. Elle garantit en gros que la fonction s'executera jusqu'a la fin et qu'il n'y aura pas de return surprise mais elle oblige dans la majorite des cas a creer au moins une variable supplementaire pour contenir la valeur de retour. ''fin bref ! Fort etrange de mon point de vue mais ca doit sans doute avoir son interet !

    Citation Envoyé par koala01 Voir le message
    En fait, c'est normal...

    Ce qu'il faut savoir, c'est que contrairement à la fonction if(test), l'opérateur ternaire permet de créer des constantes de compilation.

    Pour que cela puisse fonctionner avec les constantes de compilation (donc, en dehors de tout contexte d'exécution dynamique!!!), il faut:
    que les types des deux opérandes soient identiques
    que le compilateur trouve dans le code toutes les informations qui lui permettront de fournir la constante demandée
    Or, B* (un pointeur sur un objet de type B) et C* (un pointeur sur un objet de type C) sont... deux types absolument différents, et ce, même s'ils ont une base commune.
    Je pense que je pense trop "C" encore. Prenons un exemple (tres) grossier : c'est un peu comme si on retournait un pointeur void* provenant d'un char* ou d'un int*. Pour illustrer mes propos :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int *funcInt()
    {
      return malloc(sizeof(int));
    }
     
    char *funcChar()
    {
      return malloc(sizeof(char));
    }
     
    void *func(int i)
    {
      return (i ? funcInt() : funcChar());
    }
    Ce code passera comme une lettre a la poste en C mais son equivalent C++ n'appreciera pas. C'est a cause de cette logique que je ne reussissais pas a comprendre. "Puisqu'ils ont le meme parent, pourquoi je ne peux pas les mettre dans un ternaire ?". Encore des subtilites du C++ qu'il me faut apprendre.

    Citation Envoyé par Flob90
    Cette différence du ternaire par rapport à un if que tu viens d'expérimenter est tout simplement le fait que cette structure conditionnelle a un typage plus fort que la structure plus "classique" du if (en C++ du moins).

    C'est pour moi la seule raison qui fait que j'ai tendance à en utiliser : je sais que je vais faire une structure conditionnel ayant un type bien définie ? Alors j'utilise un ternaire pour que le compilateur m'indique si quelque chose ne va pas.
    Je ne pensais pas le ternaire complique a ce point ni meme qu'il pouvait servir a ca ! Pour moi ca se cantonnait a un if/else un peu plus rapide (faut vraiment que j'aille verifier cette info !). Finalement j'ai bien fait de faire ce sujet !

    Merci pour vos eclaircissements !

    EDIT: J'ai trouve sur ce sujet la preuve que je me faisais des illusions sur le ternaire. Ce n'est donc pas plus optimise qu'un if else ! Interessant en tout cas, je le recommande aux interesses.

  8. #8
    Expert éminent
    Avatar de koala01
    Homme Profil pro
    aucun
    Inscrit en
    Octobre 2004
    Messages
    11 644
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 53
    Localisation : Belgique

    Informations professionnelles :
    Activité : aucun

    Informations forums :
    Inscription : Octobre 2004
    Messages : 11 644
    Par défaut
    Citation Envoyé par imperio Voir le message
    Au debut je faisais des ternaires (en C surtout) car on m'avait dit que c'etait plus rapide a l'execution (je n'ai pas cherche a savoir si c'etait vrai, mais pour le coup je vais le faire car ca fait trop longtemps que cette question est en suspens).
    En fait, il est possible que certaines optimisations soient apportées par le compilateur avec l'opérateur ternaire qui ne pourraient peut etre (et ce n'est meme plus sur, étant donné que les compilateurs sont de plus en plus "intelligents") pas être apportées avec le if ... else.

    L'équipe (enfin, une personne de l'équipe ) a récemment traduit une série d'articles sur l'étude du C++ bas niveau.

    Un petit détour par ces traductions pourrait sans doute t'en apprendre d'avantage (dont les articles qui parlent des conditions )
    Maintenant c'est plutot pour raccourcir un peu mon code et le rendre plus lisible (au moins pour moi). Je prefere clairement ca :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    int func(int a, int b)
    {
      return (a > b ? a : b);
    }
    que :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    int func(int a, int b)
    {
      if (a > b)
        return a;
      return b;
    }
    C'est justement l'un des rares cas dans lequel il peut effectivement être utile et intéressant, avec quelques autres cas vraiment particuliers dans certaines situations particulières.

    Mais, dans le cas qui nous intéresse, avec le transtypage qu'il impose, cela devient beaucoup moins lisible à mon sens

    Tiens d'ailleurs le ternaire respecte le SESE si je comprends bien.
    Il n'y a qu'un point d'accès (l'appel de la fonction elle-même) et un point de sortie (le return), donc oui, on a bien respecté SESE
    Je ne comprends pas vraiment l'interet de cette regle de codage.
    Je vais t'avouer honnêtement que, à l'heure actuelle, je ne comprend pas non plus l'intérêt de cette règle de codage.

    Cependant, il faut se rappeler que le génie logiciel informatique a maintenant près de 60 ou 70 ans, pendant lesquels il a eu largement l'occasion d'évoluer.

    Il est donc possible de trouver une justification "historique" à cette règle, dans les faits que:
    • certains langages n'autorisaient, à leurs débuts, pas d'avoir plusieurs points de sortie à une fonction
    • les méthodes de représentations d'algorithmes d'origine (flowchart en tête) étaient beaucoup plus lisibles en respectant cette règle

    Elle garantit en gros que la fonction s'executera jusqu'a la fin et qu'il n'y aura pas de return surprise mais elle oblige dans la majorite des cas a creer au moins une variable supplementaire pour contenir la valeur de retour. ''fin bref ! Fort etrange de mon point de vue mais ca doit sans doute avoir son interet !
    A vrai dire, je crois que ca doit clairement être évalué au coup par coup, mais je comprends d'autant plus ton point de vue que je considère aussi cette règle comme totalement absurde, actuellement du moins
    Je pense que je pense trop "C" encore. Prenons un exemple (tres) grossier : c'est un peu comme si on retournait un pointeur void* provenant d'un char* ou d'un int*. Pour illustrer mes propos :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    int *funcInt()
    {
      return malloc(sizeof(int));
    }
     
    char *funcChar()
    {
      return malloc(sizeof(char));
    }
     
    void *func(int i)
    {
      return (i ? funcInt() : funcChar());
    }
    oui, parce que tu as très peu de contrôle de type en C et que la taille d'un pointeur est de toutes manière fixe et définitive (pour une architecture donnée), quel que soit le type se trouvant à l'adresse maintenue par le pointeur.

    C++ tend cependant:
    à éviter au maximum le recours à un void * car on perd "toute information" au sujet de l'objet pointé
    à inciter très largement à l'utilisation du transtypage explicite, afin que l'on puisse plus facilement les retrouver dans le code

    Ce code passera comme une lettre a la poste en C mais son equivalent C++ n'appreciera pas. C'est a cause de cette logique que je ne reussissais pas a comprendre. "Puisqu'ils ont le meme parent, pourquoi je ne peux pas les mettre dans un ternaire ?". Encore des subtilites du C++ qu'il me faut apprendre.
    Je peux te rassurer sur ce point:

    Si on apprend les bases du C++ (la syntaxe, les mots clés, peut etre même l'utilisation de la bibliothèque standard) en quelques dizaines d'heures, il faut à tout le monde plusieurs années pour "en faire le tour".

    J'en fais personnellement depuis pas mal de temps, et je rencontre encore souvent des subtilités
    A méditer: La solution la plus simple est toujours la moins compliquée
    Ce qui se conçoit bien s'énonce clairement, et les mots pour le dire vous viennent aisément. Nicolas Boileau
    Compiler Gcc sous windows avec MinGW
    Coder efficacement en C++ : dans les bacs le 17 février 2014
    mon tout nouveau blog

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

Discussions similaires

  1. [QReport] Erreur bizarre
    Par vali dans le forum Composants VCL
    Réponses: 3
    Dernier message: 01/03/2009, 01h25
  2. Erreur Bizarre 'copier-coller'
    Par papy_tergnier dans le forum C++Builder
    Réponses: 2
    Dernier message: 21/11/2005, 14h35
  3. Erreur bizarre
    Par ydjilali dans le forum Shell et commandes GNU
    Réponses: 4
    Dernier message: 06/10/2005, 14h52
  4. Le kernel erreur bizarre au chargement d'ext3
    Par Invité dans le forum Administration système
    Réponses: 6
    Dernier message: 01/09/2004, 16h54
  5. [appel de fonction]Erreur bizarre
    Par DEC dans le forum ASP
    Réponses: 4
    Dernier message: 10/08/2004, 17h08

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