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 :

Condition pour être "swappable"


Sujet :

Langage C++

  1. #1
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 492
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 492
    Billets dans le blog
    1
    Par défaut Condition pour être "swappable"
    Bonjour,

    Ce code ne compile pas :
    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 <type_traits>
     
    class Foo {
    public:
    	Foo(int& r) :
    			ref(r) {
    	}
    	int& ref;
    };
     
    namespace std {
    void swap(Foo& lhs, Foo& rhs) {
    	(void) lhs;
    	(void) rhs;
    }
    }
     
    int main() {
    	static_assert(std::is_swappable<Foo>(), "Foo");
     
    	int v = 42;
    	Foo e(v), f(v);
    	std::swap(e, f);
    }
    Voici l'erreur remontée :
    g++ -std=c++17 -O2 -Wall -pedantic -pthread main.cpp && ./a.out
    main.cpp: In function 'int main()':
    main.cpp:19:2: error: static assertion failed: Foo
      static_assert(std::is_swappable<Foo>(), "Foo");
    Si j'enlève l'assertion statique, le compile et s'exécute en passant bien dans ma fonction swap() custom. D'après la doc de std::is_swappable, je pensais que mon code aurait dû être OK.

    Pourriez-vous m'expliquer pourquoi ce code ne compile pas ?



    J'ai ensuite enlevé ma fonction swap() custom. Le code ne compile plus car aucune surcharge correcte de swap() n'est trouvée. D'après mes tests, c'est parce que la classe contient un champ de type int& mais ne possède pas d'opérateur de copie.

    Est-ce correct ? Pas besoin d'un constructeur par copie en plus ?

    Merci d'avance !

  2. #2
    Rédacteur/Modérateur


    Homme Profil pro
    Network game programmer
    Inscrit en
    Juin 2010
    Messages
    7 145
    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 145
    Billets dans le blog
    4
    Par défaut
    Le principe d'une référence c'est d'être assignée et initialisée à sa construction, puis sa valeur ne peut plus bouger. Donc le compilo aura bien du mal à implémenter un mouvement par défaut. Donc c'est à toi d'implémenter constructeur et opérateur de mouvement.
    Ou de surcharger swap comme tu l'as fait.

    Pour le premier problème, peut-être en spécifiant qu'il s'agit d'une surcharge de template<> swap afin d'aider le compilo ? Sinon je ne vois pas. (Et je viens de voir que ceci va en ce sens https://stackoverflow.com/a/8439357)
    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
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 492
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 492
    Billets dans le blog
    1
    Par défaut
    Je suis allé plus loin dans mes recherches et j'ai effectivement vu que la réponse à la question 2 n'est pas dans les opérations de copie mais bien de déplacement. Ce code compile très bien :
    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
    #include <iostream>
    #include <type_traits>
     
    class Foo {
    public:
    	// Ctor
    	Foo(int& r) :
    			ref(r) {
    	}
     
    	// Copy
    	Foo(const Foo& other) = delete;
    	Foo& operator=(const Foo& other) = delete;
     
    	// Move
    	Foo(Foo&& other) = default;
    	Foo& operator=(Foo&& other) {
    		(void) other;
    		return *this;
    	}
     
    private:
    	int& ref;
    };
     
    int main() {
    	static_assert(std::is_swappable<Foo>(), "Foo not swappable");
     
    	int v = 42;
    	int w = 66;
    	Foo e(v), f(w);
     
    	std::swap(e, f);
    }

    Il reste donc à répondre à la question 1

    Je viens de tester de rajouter template<> et ça ne règle pas le problème. Si les opérations de déplacement sont présentes, ça fonctionne bien entendu, mais en les supprimant j'ai une erreur géniale :
    g++ -std=c++17 "-IC:\\Users\\Greextpgr\\workspace\\Tests/" -O0 -g3 -pedantic -Wall -Wextra -c -fmessage-length=0 -o main.o "..\\main.cpp" 
    ..\main.cpp:28:6: error: template-id 'swap<>' for 'void std::swap(Foo&, Foo&)' does not match any template declaration
     void swap(Foo& lhs, Foo& rhs) {
          ^~~~
    En revanche, en se plaçant dans une version qui compile et en mettant un std::cout << __PRETTY_FUNCTION__ << std::endl; dans le corps de la fonction swap(), on passe de
    void std::swap(Foo&, Foo&)
    à
    typename std::enable_if<std::__and_<std::__not_<std::__is_tuple_like<_Tp> >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> >::value>::type std::swap(_Tp&, _Tp&) [with _Tp = Foo; typename std::enable_if<std::__and_<std::__not_<std::__is_tuple_like<_Tp> >, std::is_move_constructible<_Tp>, std::is_move_assignable<_Tp> >::value>::type = void]
    Un tel nom ne donne envie de dire que même si un swap() custom est fourni, il faut être move assignable et move constructible. Et c'est cohérent avec le message d'erreur ci-dessus...

  4. #4
    Modérateur

    Avatar de Bktero
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2009
    Messages
    4 492
    Détails du profil
    Informations personnelles :
    Sexe : Homme
    Âge : 37
    Localisation : France, Loire Atlantique (Pays de la Loire)

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

    Informations forums :
    Inscription : Juin 2009
    Messages : 4 492
    Billets dans le blog
    1
    Par défaut
    Je suis allé encore plus loin et je me suis attaché à la partie en gras ci-dessous (http://en.cppreference.com/w/cpp/algorithm/swap):
    1) Swaps the values a and b. This overload does not participate in overload resolution unless std::is_move_constructible_v<T> && std::is_move_assignable_v<T> is true. (since C++17)
    J'ai donc écrit ce code :
    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
    #include <utility>
    #include <type_traits>
     
    class Foo {
    public:
    	// Ctor
    	Foo(int& r) :
    			ref(r) {
    	}
     
    	// Copy
    	Foo(const Foo& other) = delete;
    	Foo& operator=(const Foo& other) = delete;
     
    	// Move
    	Foo(Foo&& other) = delete;
    	Foo& operator=(Foo&& other) = delete;
     
    private:
    	int& ref;
    };
     
    namespace std {
    template<>
    void swap(Foo& lhs, Foo& rhs) {
    	(void) lhs;
    	(void) rhs;
    }
    }
     
    int main() {
    	int v = 42;
    	int w = 66;
    	Foo e(v), f(w);
     
    	std::swap(e, f);
    }
    Et je l'ai compilé sur Compiler Explorer avec l'option -std=c++11 à l'aide de gcc 5.5 : pas de soucis et ça me semble correct. Avant C++17, pas besoin d'être move assignable et move constructible.

    En revanche, avec la même option mais en utilisant gcc 6 (idem avec gcc 7), j'ai de nouveau l'erreur de template non-reconnu. La branche 6 semble être la première de gcc à supporter des éléments de C++17. A part un bug de rétro-compatibilité, je ne vois pas trop d'autres solutions... Sauf que le problème est le même avec clang 6 sur ce même Compiler Explorer donc là je suis un peu perdu...

    Autre solution : j'interprète mal, il a toujours fallu être move assignable et move constructible et c'est gcc 5.5 qui avait un bug et vérifiait mal ces conditions.

  5. #5
    Membre Expert
    Homme Profil pro
    Ingénieur développement logiciels
    Inscrit en
    Juin 2011
    Messages
    750
    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 : 750
    Par défaut
    C'est un problème d'ADL. Mettre une fonction swap dans le namespace std ne garantit pas qu'elle soit utilisée. Par exemple, std::iter_swap utilisera la version définie dans SL. En fait, toute partie de la SL qui se sert de swap utilisera celle dans la SL et jamais la surcharge qui va bien.

    Pour que la bonne version de swap soit utilisée, il ne faut pas le mettre dans le namespace std, mais dans celui où est définie l'objet Foo. L'ADL fait le reste.

    Par contre, au niveau code utilisateur, il ne faut pas appeler directement std::swap, mais juste swap (le compilateur ira chercher le bon namespace: ADL). Et pour une utilisation générique, il faut au préalable mettre using std::swap;.

    On peut aussi faire 2 fonctions swap, une dans le namespace de Foo, l'autre dans std. Je trouve cela plutôt tordu alors --à défaut de trouver mieux-- je préfère utiliser une fonction intermédiaire qui s'occupe de faire using et appeler la bonne surcharge.

    https://jonathanpoelen.github.io/201...%A9n%C3%A9ral/

  6. #6
    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
    Dans ton premier exemple, tu ajoutes dans std un overload d'une fonction qui y existe déjà. Ce n'est pas autorisé par la norme. Dans ton dernier, tu ajoute une spécialisation d'une fonction template. Même si c'est encore autorisé (probablement plus pour longtemps, voir plus loin), ça ne fait probablement pas ce que tu attends.

    Il faut faire comme te l'indique jo_link_moir.

    Il y a un papier qui parle justement de ce problème : http://www.open-std.org/jtc1/sc22/wg...18/p0551r2.pdf Il a été accepté lors de la dernière réunion (même si je ne suis pas certain de totalement l'apprécier, il faut que je le relise calmement).
    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.

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