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

C++ Discussion :

Erreur compilation - surcharge opérateur


Sujet :

C++

  1. #1
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut Erreur compilation - surcharge opérateur
    Bonjour à tous.

    Je bloque sur qqch qui paraît simplissime, mais je n'arrive pas à trouver la source du problème.
    J'essaie tout simplement de surcharger un opérateur + pour un type que j'ai définit.

    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
    #include <iostream>
    #include <cassert>
    #include <math.h>
     
    typedef unsigned char PixelNB;
    typedef double PixelFloat;
     
    inline PixelNB operator*(const PixelNB& pix, double a){
    		assert(a>=0);
    		int gris=std::min( static_cast< int >(round(a*pix)) , 255 );
    		PixelNB pix_res=gris;
    		return pix_res;
    };
     
    int main( int argc, char* argv[] ){
    		PixelNB pixNB=77;
    		std::cout << "Hello World !" << std::endl;
    }
    A la compilation :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    main.cc:10:16: error: overloaded 'operator*' must have at least one parameter of class or enumeration type
    inline PixelNB operator*(const PixelNB& pix, double a){
                   ^
    1 error generated.
    Mon compilateur (sous MAC) :
    Apple LLVM version 5.1 (clang-503.0.40) (based on LLVM 3.4svn)
    Target: x86_64-apple-darwin12.6.0


    Le but pour la suite serait de définir cet opérateur pour d'autre type de pixel et de pouvoir ajouter ceci :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    inline PixelFloat operator*(const PixelFloat& pix, double a){
    		return a*pix;
    };
     
    template <typename PixelType>
    inline PixelType operator*(double a, const PixelType& pix){ return pix*a; };
    Merci de votre aide.

  2. #2
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Salut,

    comme l'indique l'erreur, tu ne peux surcharger un opérateur que pour un type utilisateur. Un typedef n'est pas un type utilisateur.
    Il te faut une class/struct/union ou enum. Encore une fois, c'est écrit dans le message d'erreur.
    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.

  3. #3
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    un typedef n'est pas un nouveau type.
    C'est un nouveau nom de type.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    typedef unsigned char PixelNB;
    PixelNB operator*(const PixelNB& pix, double a);
    Ceci est strictement identique à:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    unsigned char operator*(const unsigned char& pix, double a);
    aucun des deux types unsigned char et double n'est un "user defined type", c'est à dire une classe, une structure, une enum ou une union.

  4. #4
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    Donc en gros si je vous suis bien je suis en train de vouloir redéfinir un opérateur interne au C++ (le produit d'un uchar avec un double).
    Est-ce possible de le faire ? Visiblement non par le moyen que j'essaie, mais y-a-t-il un autre moyen ?

    Si j'englobe la chose dans un struct, ou une classe (je ne maitrise pas du tout les enum, mais je ne pense pas que la solution réside là):

    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
    class PixelNB{
    public:
    	template <typename T>
    	PixelNB( T c ):_data(c){};
     
    	unsigned char data() const { return _data; };
    	void PrintPixel(){ std::cout << (int) _data << std::endl ; };
     
    private:	
    	unsigned char _data;
    };
     
    PixelNB operator+( const PixelNB& pix1, const PixelNB& pix2  ){ 
    	return PixelNB( pix1.data()+pix2.data() ); };
     
    inline PixelNB operator*(const PixelNB& pix, double a){
    		assert(a>=0);
    		int gris=std::min( static_cast< int >(round(a*pix.data())) , 255 );
    		PixelNB pix_res=gris;
    		return pix_res;
    };
     
    int main( int argc, char* argv[] ){
    		PixelNB pix1(77);
    		PixelNB pix2(43);
     
    		pix1.PrintPixel();
    		PixelNB pix3( pix1+pix2 );
    		pix3.PrintPixel();
    		PixelNB pix4( pix1*0.3 );
    		pix4.PrintPixel();
    }
    Alors ca me complique beaucoup les choses car je dois réécrire tous les autres opérateurs (du style l'affection par =, somme de deux uchar, produit, etc ... ) en version unaire et binaire, sans compter les cast ... Et pour mon type PixelFloat qui devra devenir lui aussi une classe ce sera pareil. Et si j'ai un 3eme type de pixel, ca va vite devenir très très lourd. Donc je ne pense pas que la classe soit la bonne solution.

  5. #5
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

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

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Par défaut
    Salut,

    Heureusement que l'on ne peut pas redéfinir des opérateurs sur des types de bases! Image un peu le bazar que cela produirait pour les autres utilisateurs (s'il y en a... mais ça c'est une autre histoire).

    Sinon, pour ton problème de PixelNB et PixelFloat, tu peux tout simplement faire un template:

    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 TYPE>
    class PixelT
    {
    public:
      PixelT(const T & c) : m_data(static_cast<T>(c)
      {}
     
      virtual ~PixelT()
      {}
     
    private:
       T m_data;
    };
     
    template<typename TYPE>
    PixelT<TYPE> operator*(const PixelT<TYPE> & pix, double a)
    {
       assert(a>=0); // Préfère l'utilisation d'exception, ça évite de planter ton programme
       TYPE gris=std::min( static_cast< TYPE >(round(a*pix)) , static_cast<TYPE>(255) );
       Pixel<TYPE> pix_res(gris);
       return pix_res;
    }
     
     
    typedef PixelT<unsigned char> PixelNB;
    typedef PixelT<float> PixelFloat;
    typedef PixelT<double> PixelDouble;

  6. #6
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    En partant de ton code darkman, voici quelques modifications qui permettent de le rendre compilable :
    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
    template<typename TYPE>
    class PixelT{
    public:
    	template<typename T>
      PixelT(const T & c) : m_data(static_cast<TYPE>(c)){};
      void PrintPixel();
      virtual ~PixelT(){};
     
      TYPE data() const { return m_data; };
     
    private:
       TYPE m_data;
    };
     
    template<typename TYPE>
    PixelT<TYPE> operator*(const PixelT<TYPE> & pix, double a){
       assert(a>=0); // Préfère l'utilisation d'exception, ça evite de planter ton programme
       TYPE gris=std::min( static_cast< TYPE >(round(a*pix.data())) , static_cast<TYPE>(255) );
       PixelT<TYPE> pix_res(gris);
       return pix_res;
    }
     
    template<typename TYPE>
    void PixelT<TYPE>::PrintPixel(){ std::cout << m_data << std::endl ; }
     
    template<>
    void PixelT<unsigned char>::PrintPixel(){ std::cout << (int) m_data << std::endl ; }
     
    typedef PixelT<unsigned char> PixelNB;
    typedef PixelT<float> PixelFloat;
    typedef PixelT<double> PixelDouble;
     
    int main( int argc, char* argv[] ){
     		PixelNB pix1(77);
     		PixelNB pix2(43);
     
     		pix1.PrintPixel();
    		//PixelNB pix3( pix1+pix2 );
    		//pix3.PrintPixel();
    		PixelNB pix4( pix1*0.3 );
    		pix4.PrintPixel();
    }
    Donc effectivement ca réponds au problème : Quelle structure adopter ?
    Par contre ca m'oblige à redéfinir tous les autres opérateurs encore ...
    Du style pour l'addition:
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    template<typename TYPE>
    PixelT<TYPE> operator+( const PixelT<TYPE>& pix1, const PixelT<TYPE>& pix2  ){ 
    return PixelT<TYPE>( pix1.data()+pix2.data() ); }
    Dans mon cas je veux juste modifier ce qui se passe lors de la multiplication par un scalaire.
    Pour tous les autres opérateurs (dont j'ai besoin également) je veux garder le comportement natif.

    Question naive du coup qui me vient à l'esprit : Ai-je moyen de templatiser l'opérateur lui-même ?
    Ca permettrait de créer une méthode qui, pour tout opérateur, appliquerait les effets de l'opération sur le m_data.
    Et je pourrais spécialiser le cas produit par un scalaire.

  7. #7
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    Alors ca me complique beaucoup les choses car je dois réécrire tous les autres opérateurs (du style l'affection par =, somme de deux uchar, produit, etc ... ) en version unaire et binaire, sans compter les cast ...
    Ou alors tu la joue malin. Tu fais un opérateur de conversion, et toutes les opérations que tu ne redéfinis pas seront possible via la conversion implicite.
    Citation Envoyé par lg_53 Voir le message
    Et pour mon type PixelFloat qui devra devenir lui aussi une classe ce sera pareil. Et si j'ai un 3eme type de pixel, ca va vite devenir très très lourd.
    Encore un peu de malinerie : 3 trucs similaires dont seul le type change ? Hmmmm ça serait pas du template ça ?
    Citation Envoyé par lg_53 Voir le message
    Donc je ne pense pas que la classe soit la bonne solution.
    Tu es libre de penser, mais c'est la seule solution.
    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.

  8. #8
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

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

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    Par contre ca m'oblige à redéfinir tous les autres opérateurs encore ...
    Oui mais en même temps, je ne pense pas que tu en ai 50 à surcharger: +, -, *, /, +=, -=, *=, /=.
    De plus, même si tu ne les redéfinis pas (tous voire aucun), tu n'as juste qu'à appeler ton accesseur data() et faire l'opération dessus.

    Citation Envoyé par lg_53 Voir le message
    Question naive du coup qui me vient à l'esprit : Ai-je moyen de templatiser l'opérateur lui-même ?
    Ca serait vraiment une bonne idée mais malheureusement ça n'est pas possible (à ma connaissance). Mais bon, je ne vois pas comment cela serait possible. Certains opérateurs renvoient des booléens, d'autres un objet etc...

    Edit: l'opérateur de conversion comme dit Bousk est une bonne idée et simple à mettre en place.

  9. #9
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    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
    template<typename TYPE>
    class PixelT{
    public:
      template<typename T>
      PixelT(const T & c) : m_data(static_cast<TYPE>(c)){};
     
      template<typename T>
      PixelT(const PixelT<T> & c) : m_data( static_cast<TYPE>(c.data()) ){};
     
      virtual ~PixelT(){};
     
      void PrintPixel(std::ostream& os=std::cout) const; 	
      friend std::ostream& operator << (std::ostream& os, const PixelT& pix){
    		 pix.PrintPixel(os); return os;
      };
     
      TYPE data() const { return m_data; };
     
      operator TYPE() { return m_data ; }; 
     
    private:
       TYPE m_data;
    };
     
    template<typename TYPE>
    PixelT<TYPE> operator*(const PixelT<TYPE> & pix, double a){
       PixelT<TYPE> pix_res(a*pix.data());
       return pix_res;
    }
     
    template<>
    PixelT<unsigned char> operator*(const PixelT<unsigned char> & pix, double a){
       assert(a>=0); // Préfère l'utilisation d'exception, ça evite de planter ton programme
       unsigned char gris=std::min( static_cast< int >(round(a*pix.data())) , static_cast< int >(255) );
       PixelT<unsigned char> pix_res(gris);
       return pix_res;
    }
     
    template<typename TYPE>
    void PixelT<TYPE>::PrintPixel(std::ostream& os) const { os << m_data ; }
     
    template<>
    void PixelT<unsigned char>::PrintPixel(std::ostream& os) const { os << (int) m_data ; }
     
    typedef PixelT<unsigned char> PixelNB;
    typedef PixelT<float> PixelFloat;
    typedef PixelT<double> PixelDouble;
     
    template<typename TYPE>
    void PrintPixel(const PixelT<TYPE> pix ){ pix.PrintPixel() ; };
     
    int main( int argc, char* argv[] ){
     		PixelNB pix1(77);
     		PixelNB pix2(43);
     		PixelDouble pix_d0( pix1 ); // Le cast n'est pas effectuer lors d'une construction => besoin d'ajouter un constructeur PixelT(const PixelT<T> & c) 
     		PixelDouble pix_d1( (int) pix1); // Le cast PixelNB --> int fonctionne donc, en passant par PixelNB--> double --> int
     
     		std::cout << "Pix1 (NB) : " << pix1 << std::endl;
     		std::cout << "Pix2 (NB) : " << pix2 << std::endl;
     		std::cout << "Pix1 + Pix2 : " << pix1 + pix2 << std::endl;
    		//PrintPixel( pix1+pix2 );  /// <-- erreur car pix1+pix2 renvoie un uchar. Et donc la methode PrintPixel ne peut etre appelee
    		PixelNB pix3( pix1+pix2 );
    		std::cout << "Pix3 (NB) : " << pix3 << std::endl;
    		std::cout << "Pix1*10.3 : " << pix1*10.3 << std::endl;
    		std::cout << "Pix1*0.3 : " << pix1*0.3 << std::endl;
    		PixelNB pix4( pix1*10.3 );
    		std::cout << "Pix4 (NB) : " << pix4 << std::endl;
    		std::cout << "Pix_d0 (Pix_double) : " << pix_d0 << std::endl;
    		std::cout << "Pix_d1 (Pix_double) : " << pix_d1 << std::endl;
    		std::cout << "Pix_d1*10.3 : " << pix_d1*10.3 << std::endl;
    		std::cout << "Pix_d1*0.3 : " << pix_d1*0.3 << std::endl;
    		PixelDouble pix_d2( pix_d1*10.3 );
    		std::cout << "Pix_d2 (Pix_double) : " << pix_d2 << std::endl;
    }
    Effectivement après avoir creusé du côté de la surcharge des cast (que je viens de découvrir grâce à vous, merci), j'ai fini par écrire le code ci-dessus.
    Ensuite, j'ai bien sûr essayer de manipuler tout cela, et ça ne se passe pas trop mal.

    Voici mes remarques/observations que j'ai pu faire en manipulant ce code. Corrigez-moi si elles sont fausses ou imprécises :
    1) Si je comprends bien comment les choses se passent, à chaque fois que le compilateur est face à une opération non définie dans ma classe, il convertit mon PixelT<TYPE> en un TYPE (ce qui est donc la même chose que ce que je faisais avec la méthode data()) et tente l'opération.
    Donc ca automatise l'appel à la méthode data() via le cast implicite.

    2) Pour cause de conflit, je ne peux pas surcharger à la fois le cast en double, le cast en int et le cast en uchar.
    En effet, si je le fais et qu'ensuite j'écris qqch comme "pixel1+pixel2" mon compilateur ne saura pas quoi prendre comme cast pour pouvoir ensuite faire la somme.
    J'ai donc opté pour un cast du type lié au pixel : operator TYPE()

    3) Le cast ne semble pas s'effectuer lorsqu'on essaie de construire un certain type de pixel à partir d'un autre type de pixel.
    Si on écrit
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    PixelDouble pix_d( pix ); //// avec pix, un PixelNB
    alors on s'attends à ce que le compilateur convertisse l'argument pix en uchar, pour ensuite appelé le constructeur PixelT(const T & c) de PixelT.
    Visiblement ce n'est pas le cas et j'ai dû rajouté un constructeur PixelT(const PixelT<T> & c)...

    4) Petit bémol (illustrer par la ligne commentée dans le code) : Soient Pixel1 et Pixel2, deux PixelT< uchar >, alors leur somme Pixel1+Pixel2 n'est pas un PixelT< uchar > mais simplement un uchar ! Donc il faut bien repenser à passer par une variable intermédiaire, avant de se servir de la somme comme d'un PixelT< uchar >.

    Merci pour vos retours.

  10. #10
    Rédacteur/Modérateur


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

    Informations professionnelles :
    Activité : Network game programmer

    Informations forums :
    Inscription : Juin 2010
    Messages : 7 153
    Billets dans le blog
    4
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    2) Pour cause de conflit, je ne peux pas surcharger à la fois le cast en double, le cast en int et le cast en uchar.
    En effet, si je le fais et qu'ensuite j'écris quelque chose comme "pixel1+pixel2" mon compilateur ne saura pas quoi prendre comme cast pour pouvoir ensuite faire la somme.
    J'ai donc opté pour un cast du type lié au pixel : operator TYPE()
    c'est exactement ce qu'on disait plus haut en fait..

    Citation Envoyé par lg_53 Voir le message
    3) Le cast ne semble pas s'effectuer lorsqu'on essaie de construire un certain type de pixel à partir d'un autre type de pixel.
    Si on écrit
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    PixelDouble pix_d( pix ); //// avec pix, un PixelNB
    alors on s'attend à ce que le compilateur convertisse l'argument pix en uchar, pour ensuite appelé le constructeur PixelT(const T & c) de PixelT.
    Visiblement ce n'est pas le cas et j'ai dû rajouté un constructeur PixelT(const PixelT<T> & c)...
    Une classe a toujours un constructeur par copie. Il sera utilisé avant une quelconque conversion implicite.

    Citation Envoyé par lg_53 Voir le message
    4) Petit bémol (illustrer par la ligne commentée dans le code) : Soient Pixel1 et Pixel2, deux PixelT< uchar >, alors leur somme Pixel1+Pixel2 n'est pas un PixelT< uchar > mais simplement un uchar ! Donc il faut bien repenser à passer par une variable intermédiaire, avant de se servir de la somme comme d'un PixelT< uchar >.
    Il n'y a aucune raison qu'il te crée un Pixel s'il peut se contenter d'un uchar.
    D'ailleurs si tu veux choper la somme dans un PixelT<uchar> tu as surement une syntaxe PixelT<uchar> pix = pix1 + pix2; donc je vois pas où se situe ton problème de variable intermédiaire ?

    Et je comprends pas non plus pourquoi tu n'as pas un "simple" constructeur PixelT(const TYPE& v) : m_data(v) {} c'est p-e juste ça qui te manque.. le compilo il est sympa, il fait 1 conversion implicite, pas 2
    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.

  11. #11
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Pas tout à fait.
    Le compilateur peut faire plusieurs étapes de conversions implicites, mais une seule peut être définie dans une classe (user defined type)
    Il peut y avoir, avant et après la conversion "user defined", une conversion version en lvalue, une promotion numérique ou des ajustements de qualifications (const, volatile...)

    Plus de détails dans user defined conversion sur cppreference.

  12. #12
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    Citation Envoyé par Bousk Voir le message
    Il n'y a aucune raison qu'il te crée un Pixel s'il peut se contenter d'un uchar.
    D'ailleurs si tu veux choper la somme dans un PixelT<uchar> tu as surement une syntaxe PixelT<uchar> pix = pix1 + pix2; donc je vois pas où se situe ton problème de variable intermédiaire ?
    Une indirection ou une variable intermédiaire dans le sens où :
    Si j'ai une fonction
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    template <typename T> void foo(const PixelT<T>& pix ){}
    alors je ne peux pas l'appeler comme ça :
    foo(pix1+pix2) car la somme renvoie un uchar (si pix1 et pix2 sont des PixeT<uchar>) alors que ma fonction foo attends un PixelT.
    Je suis donc contraint de passer par une variable de transition :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    PixelT<uchar> pix3 = pix1 + pix2 ;
    foo(pix3);

    Pour les constructeurs de ma classe :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
      template<typename T>
      PixelT(const T & c) : m_data(static_cast<TYPE>(c)){};
      PixelT(const TYPE& v) : m_data(v) {};
      template<typename T>
      PixelT(const PixelT<T> & c) : m_data( static_cast<TYPE>(c.data()) ){};  /// constructeur de copie
    Le second constructeur que tu m'as conseillé de rajouter: je ne l'avais pas mis car si je prends le premier dans le cas ou T=TYPE; et bien ça fait pareil non ? ...

  13. #13
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Tu n'as pas de constructeur de copie correct:
    1. template<typename T> PixelT(const T & c) : m_data(static_cast<TYPE>(c)){}; ceci est un constructeur de conversion (et d'assemblage).
    2. PixelT(const TYPE& v) : m_data(v) {}; ceci est aussi un constructeur de conversion (et d'assemblage).
    3. template<typename T> PixelT(const PixelT<T> & c) : m_data( static_cast<TYPE>(c.data()) ){}; contrairement à ton commentaire, ceci converti un PixelT<T> en un PixelT<Type>. C'est donc surtout un constructeur de conversion

    Il te faudrait explicitement un PixelT(PixelT const& source) : m_data(source.m_data) {}.

    Ce sont les constructeurs 1 et 3 qui te posent problèmes. Il permettent de convertir implicitement n'importe quoi en PixelT<n'importe_quoi>.
    D'ailleurs, 3 est une surcharge de 1 pour 1::T = PixelT<...>.

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
     
    template <typename T>
    void foo(const PixelT<T>& pix ){}
    int main() {
       PixelT<int> pix1(3);//appelle PixelT<int>::PixelT(TYPE const& v)
       PixelT<int> pix1(5ul);//appelle PixelT<int>::PixelT<T>(T const&) avec T = unsigned long int
     
       foo(pix1 + pix2);
     
       PixelT<std::string> troll(std::cout);//appelle PixelT<std::string>::PixelT<T>(T const&) avec T = std::basic_ostream<...>.
    }
    troll ne compilera pas, mais pas parce que la fonction n'existe pas, mais à cause de static_cast<std::string>(std::cout).

    Comme le compilateur peut construire un PixelT<T> pour n'importe quel type, il ne peut pas savoir quel type T prendre.
    En général, dans une classe template, tu ne veux pas une template de constructeur.

    Une forme simple serait:
    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
    template <typename T>
    class PixelT {
    public:
        typename T value_type;
    private:
        value_type value;
     
    public:
        PixelT(value_type const& value) : value(value) {}
        PixelT(PixelT const& other) = default;
        PixelT& operator = (PixelT const& other) = default;
     
        PixelT(value_type && value) : value(value) {}
        PixelT(PixelT && other) = default;
        PixelT& operator = (PixelT && other) = default;
     
        //je crois qu'il manque d'autres fonctions pour avoir un move correct
     
        ~PixelT() = default;
     
        operator value_type () {return value;}
    //si ce dernier ne compile pas, utilise operator T().
    //si celui ne compile pas non plus, hurle un coup, mais de toute facon il y a une autre solution discutable, et au moins une solution non opérateur
        value_type const& operator () () const {return value;}
        value_type & operator () () {return value;}
     
        value_type const& get() const {return value;}
        value_type & get() {return value;}
    };
    Question à ceux qui me corrigent d'habitude:
    c'est bon, j'ai bien fait mon move?
    Soyez indulgent, c'est la première fois que j'en écris un.

  14. #14
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    Citation Envoyé par leternel Voir le message
    Il te faudrait explicitement un PixelT(PixelT const& source) : m_data(source.m_data) {}.
    Ce n'est pas un cas particulier du constructeur 3. ? Pour T=TYPE ? Le static_cast ne fait rien puisqu'on lui demande de transformer un TYPE en TYPE. DOnc ma fonction template devient équivalente au constructeur de copie que tu propose, non ?

    Chez moi, ça, ça ne compile pas ... (pour pix1 et pix2 des PixelT<uchar> ) :
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    main.cc:209:3: error: no matching function for call to 'foo'
                    foo(pix1 + pix2);
                    ^~~
    main.cc:192:6: note: candidate template ignored: could not match 'PixelT<type-parameter-0-0>' against 'int'
    void foo(const PixelT<T>& pix ){}
         ^
    1 error generated.
    Outre cela, continuons :
    Citation Envoyé par leternel Voir le message
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    PixelT<std::string> troll(std::cout);//appelle PixelT<std::string>::PixelT<T>(T const&) avec T = std::basic_ostream<...>.
    troll ne compilera pas, mais pas parce que la fonction n'existe pas, mais à cause de static_cast<std::string>(std::cout).
    Comme le compilateur peut construire un PixelT<T> pour n'importe quel type, il ne peut pas savoir quel type T prendre.
    le compilateur peut construire un PixelT<T> pour n'importe quel type, oui. Mais là en l'occurence std::cout est du type std::basic_ostream, donc pourquoi le compilateur ne prendrait pas T égal à ca ? Ce qui semblerait naturel ...


    Citation Envoyé par leternel Voir le message
    En général, dans une classe template, tu ne veux pas une template de constructeur.

    Une forme simple serait:
    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
    template <typename T>
    class PixelT {
    public:
        typename T value_type;
    private:
        value_type value;
     
    public:
        PixelT(value_type const& value) : value(value) {}
        PixelT(PixelT const& other) = default;
        PixelT& operator = (PixelT const& other) = default;
     
        PixelT(value_type && value) : value(value) {}
        PixelT(PixelT && other) = default;
        PixelT& operator = (PixelT && other) = default;
     
        //je crois qu'il manque d'autres fonctions pour avoir un move correct
     
        ~PixelT() = default;
     
        operator value_type () {return value;}
    //si ce dernier ne compile pas, utilise operator T().
    //si celui ne compile pas non plus, hurle un coup, mais de toute facon il y a une autre solution discutable, et au moins une solution non opérateur
        value_type const& operator () () const {return value;}
        value_type & operator () () {return value;}
     
        value_type const& get() const {return value;}
        value_type & get() {return value;}
    };
    1. Ligne 4 : Je suppose que c'est un typedef au lieu du typename ? Quel est l'intérêt de ce renommage ?
    2. Je ne suis pas en C++11, donc le mot default me produit des warnings.
    En me documentant un peu, je m'aperçois que ce mot-clé dis explicitement qu'on veut la méthode créée par défaut par le compilateur.
    Donc si je supprime toutes ces lignes j'obtiens le même comportement.
    3. De même, j'ai un warning sur PixelT(value_type && value) : value(value) {}. C'est quoi cette déclaration avec une double & ?
    Ca ne fournit pas le même résultat que le constructeur PixelT(value_type const& value) : value(value) {}? Ce dernier ne suffit pas ?
    J'ai commenté la ligne également.
    4. operator value_type () {return value;} Bien sûr qu'il compile, puisque j'avais écris avant operator TYPE() { return m_data ; }; qui est exactement la même chose, juste tu as changé les notations.
    5. value_type const& operator () () const {return value;} et value_type & operator () () {return value;} : C'est quoi ces opérateurs là avec un double jeu de parenthèse ? Je ne connais pas. Ils servent à quoi ? J'ai testé ta classe avec le main que j'avais écris plus haut. En rajoutant un print dans ces méthodes, je me suis aperçu qu'on ne rentre pas dans ces méthodes...

    Citation Envoyé par leternel Voir le message
    Question à ceux qui me corrigent d'habitude:
    c'est bon, j'ai bien fait mon move?
    Soyez indulgent, c'est la première fois que j'en écris un.
    Heureux d'avoir trouvé le sujet qui t'a décider à en commencer.

  15. #15
    Membre éclairé
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Avril 2010
    Messages
    517
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Gironde (Aquitaine)

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

    Informations forums :
    Inscription : Avril 2010
    Messages : 517
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    Ce n'est pas un cas particulier du constructeur 3. ? Pour T=TYPE ? Le static_cast ne fait rien puisqu'on lui demande de transformer un TYPE en TYPE. DOnc ma fonction template devient équivalente au constructeur de copie que tu propose, non ?

    Chez moi, ça, ça ne compile pas ... (pour pix1 et pix2 des PixelT<uchar> ) :

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    4
    5
    6
    7
    main.cc:209:3: error: no matching function for call to 'foo'
                    foo(pix1 + pix2);
                    ^~~
    main.cc:192:6: note: candidate template ignored: could not match 'PixelT<type-parameter-0-0>' against 'int'
    void foo(const PixelT<T>& pix ){}
         ^
    1 error generated.
    L'exemple est incorrect: comme te le dit le compilo, la fonction foo demande un objet de type PixelT<type-parameter> (int, uchar, etc) or tu lui donnes un int (le résultat de l'addition de deux PixelT<int> converti en int par l'opérateur de conversion operator value_type()).
    Pour que l'exemple fonctionne, modifie le comme ça:

    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    foo(PixelT<int>(pix1 + pix2));

    Citation Envoyé par lg_53 Voir le message
    le compilateur peut construire un PixelT<T> pour n'importe quel type, oui. Mais là en l'occurence std::cout est du type std::basic_ostream, donc pourquoi le compilateur ne prendrait pas T égal à ca ? Ce qui semblerait naturel ...
    Le problème c'est qu'avec le std::basic_ostream, c'est qu'on ne peut pas le copier.

    Citation Envoyé par lg_53 Voir le message
    1. Ligne 4 : Je suppose que c'est un typedef au lieu du typename ? Quel est l'intérêt de ce renommage ?
    Pour que ce soit plus lisible dans certains cas. (Et c'est bien un typedef). Sinon, ce n'est pas obligatoire.

    Citation Envoyé par lg_53 Voir le message
    2. Je ne suis pas en C++11, donc le mot default me produit des warnings.
    En me documentant un peu, je m'aperçois que ce mot-clé dis explicitement qu'on veut la méthode créée par défaut par le compilateur.
    Donc si je supprime toutes ces lignes j'obtiens le même comportement.
    Si tu as la possibilité, passe par un compilateur qui gère le C++11 (on est en 2015 ^^).

    Citation Envoyé par lg_53 Voir le message
    3. De même, j'ai un warning sur PixelT(value_type && value) : value(value) {}. C'est quoi cette déclaration avec une double & ?
    Ca ne fournit pas le même résultat que le constructeur PixelT(value_type const& value) : value(value) {}? Ce dernier ne suffit pas ?
    J'ai commenté la ligne également.
    C'est un constructeur de déplacement (cherche move constructor c++11).

    Citation Envoyé par lg_53 Voir le message
    5. value_type const& operator () () const {return value;} et value_type & operator () () {return value;} : C'est quoi ces opérateurs là avec un double jeu de parenthèse ? Je ne connais pas. Ils servent à quoi ? J'ai testé ta classe avec le main que j'avais écris plus haut. En rajoutant un print dans ces méthodes, je me suis aperçu qu'on ne rentre pas dans ces méthodes...
    Ceux sont des accesseurs en lecture seul ou en lecture/écriture. Tu l'utilises de la façon suivante:

    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
     
    template<typename T>
    void print(const PixelT<T> & pix)
    {
       std::cout << pix() << std::endl;
    }
     
    template<typename T>
    void changeValue(PixelT<T> & pix, const T & value)
    {
       pix() = value;
    }
     
    int main()
    {
        PixelT<int> pix(42);
        print(pix); //Affiche 42;
        changeValue(23);
        print(pix); //Affiche 23;
    }

  16. #16
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Citation Envoyé par lg_53 Voir le message
    1. Ligne 4 : Je suppose que c'est un typedef au lieu du typename ? Quel est l'intérêt de ce renommage ?
    C'est bien un typedef, et ca permet de travailler comme avec la STL

    Cela permet d'avoir un type directement lié à l'instance de la template de classe.
    C'est très pratique pour l'utilisateur, qui peut décider de faire un typedef sur ta classe.
    Code : Sélectionner tout - Visualiser dans une fenêtre à part
    1
    2
    3
    typedef PixelT<int> pixel_t;
    pixel_t::value_type distance(pixel_t const& a, pixel_t const& b);
    pixel_t::value_type abs_distance(pixel_t const& a, pixel_t const& b);
    Demain, il change int par long, son code est parfaitement stable.

    Citation Envoyé par lg_53 Voir le message
    2. Je ne suis pas en C++11, donc le mot default me produit des warnings.
    En me documentant un peu, je m'aperçois que ce mot-clé dis explicitement qu'on veut la méthode créée par défaut par le compilateur.
    Donc si je supprime toutes ces lignes j'obtiens le même comportement.
    C'est bien cela

    Citation Envoyé par lg_53 Voir le message
    3. De même, j'ai un warning sur PixelT(value_type && value) : value(value) {}. C'est quoi cette déclaration avec une double & ?
    Ca ne fournit pas le même résultat que le constructeur PixelT(value_type const& value) : value(value) {}? Ce dernier ne suffit pas ?
    J'ai commenté la ligne également.
    le && désigne les opérations de move. En C++03, cela n'existait pas.
    Cela permet potentiellement d'éviter certaines créations de ressources chères, quand on lui transmet une variable temporaire.

    Citation Envoyé par lg_53 Voir le message
    4. operator value_type () {return value;} Bien sûr qu'il compile, puisque j'avais écris avant operator TYPE() { return m_data ; }; qui est exactement la même chose, juste tu as changé les notations.
    Tout à fait. Sauf que TYPE n'est pas un identifieur pour l'utilisateur, alors que value_type, si.

    Citation Envoyé par lg_53 Voir le message
    5. value_type const& operator () () const {return value;} et value_type & operator () () {return value;} : C'est quoi ces opérateurs là avec un double jeu de parenthèse ? Je ne connais pas. Ils servent à quoi ? J'ai testé ta classe avec le main que j'avais écris plus haut. En rajoutant un print dans ces méthodes, je me suis aperçu qu'on ne rentre pas dans ces méthodes...
    La déclaration d'un opérateur prend la notation operator @ (arguments);. avec @ le symbole de l'opérateur redéfini.
    Tu peux ainsi définir les opérateurs mathématiques (+, -, - unaire, etc), des opérateurs logiques (mais tu perdras l'évaluation abrégé (dans false && ..., le ... n'est pas évalué en temps normal), et d'autres encore, comme l'indexation via [], et surtout l'appel de fonction via ().

    Ainsi, operator () (arguments); défini l'appel de fonction sur l'objet.
    C'est comme cela que fonctionne la notion de foncteur (functor), en C++.

  17. #17
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    OK. Tout ceci est un peu redondant.
    Car si je ne m'abuse, on peut réécrire les fonctions que tu me proposes de 3 manières différentes :
    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>
    void Print(const PixelT<T> & pix){
       //std::cout << pix() << std::endl;
       //std::cout << pix.get() << std::endl;
       std::cout << pix << std::endl;
    }
     
    template<typename T>
    void ChangeValue(PixelT<T> & pix, const T & value){
       //pix() = value;
       //pix.get() = value ;
       pix = value ;
    }
    La manière non commentée, dont je préfère la forme car parfaitement transparente à l'utilisation, est équivalente aux autres (corriger moi si je me trompe) sans avoir besoin de passer par les operator () () ou bien des get () (). Pourquoi autant de redondance ?

    Me voici bien plus éclairé sur le problème. Merci à vous tous pour votre aide.

    PS : Je dispose d'une configuration machine assez particulière et il y a plein de choses incompatibles (avec diverses librairies et dépendances) ... D'où le fait que je n'ai pas C++11

  18. #18
    Expert éminent

    Femme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2007
    Messages
    5 202
    Détails du profil
    Informations personnelles :
    Sexe : Femme
    Localisation : France, Essonne (Île de France)

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

    Informations forums :
    Inscription : Juin 2007
    Messages : 5 202
    Par défaut
    Tout à fait.

    Par contre, je n'aime pas la forme par conversion, parce que justement, elle permet la conversion implicite vers le type sous-jacent.
    Ce n'est pas forcément un tord, mais dès que tu essaies de définir des opérateurs, c'est que l'objet possède une signification que tu veux controler.
    La question est dans le compromis:
    Dans une expression, veux-tu le plus longtemps possible rester avec des PixelT ou veux-tu redevenir le plus vite possible un T?

    Si tu préfères rester dans les PixelT, la conversion implicite est un soucis.

  19. #19
    Membre Expert

    Homme Profil pro
    Ingénieur calcul scientifique
    Inscrit en
    Mars 2013
    Messages
    1 229
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Localisation : France, Alpes Maritimes (Provence Alpes Côte d'Azur)

    Informations professionnelles :
    Activité : Ingénieur calcul scientifique

    Informations forums :
    Inscription : Mars 2013
    Messages : 1 229
    Par défaut
    Je veux que ca redeviennent le plus vite possible un T.

    Puisque je le rappelle au départ je voulais juste redéfinir un type d'opération particulier uniquement à savoir la multiplication entre un double et un uchar.
    Cette redéfinition ne pouvant pas être effectué directement, on est donc passé par une classe PixelT, pour pouvoir ainsi redéfinir l'opération voulue.
    Mais je veux bien sûr que toutes les autres opérations de bases se comportent comme avant i.e. comme si PixelT<uchar> était de manière transparente un uchar. D'où le fait que je favorise l'opérateur de conversion.

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

Discussions similaires

  1. Erreur de segmentation surcharge opérateur []
    Par Nicoclem dans le forum C++
    Réponses: 3
    Dernier message: 17/04/2008, 18h05
  2. surcharge opérateur erreur compilation
    Par damien77 dans le forum C++
    Réponses: 8
    Dernier message: 21/02/2007, 17h59
  3. [Débutant]Erreur compilation !
    Par gandalf_le_blanc dans le forum AWT/Swing
    Réponses: 23
    Dernier message: 30/08/2004, 14h23
  4. Trop de message d'erreurs: compilation KO
    Par jeannot27 dans le forum C++Builder
    Réponses: 6
    Dernier message: 21/01/2004, 16h45
  5. Erreur compilation DX8.1 VC++ 6
    Par d.vidal dans le forum DirectX
    Réponses: 1
    Dernier message: 10/09/2003, 09h04

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