01 août 2012

C++11 : il y a des choses qui changent

Annoncer à grand renfort de publicité (ou pas) qu'il y a eu quelques changements dans la nouvelle norme C++ 11 par rapport à la précédente norme de 2003, c'est un peu comme dire qu'il se pourrait que la lune soit distante de la terre d'un peu plus de 400 mètres : ça n'a pas grand intérêt, parce que c'est évident. Tout le monde[1] sait que C++11 apporte son lot de nouveautés, et que celles-ci sont importantes : rvalue references, le mot-clef auto, les variadic templates, la gestion de la concurrence, etc. Les additions sont nombreuses et quelques fois complexes.

Par contre, ceux qui, croyant bien faire, pensent que le nouveau standard conserve un compatibilité parfaite avec la version précédente du standard pourraient avoir quelques mauvaises surprises. Et pas seulement à la marge...

Note

[1] enfin, tous les programmeurs C++ qui s'intéressent un tant soi peu au langage lui-même

L'annexe C

Dans le standard C++ de 1998 (et dans celui de 2003, qui n'est qu'une mise à jour de celui de 98), l'annexe C a un rôle très particulier : elle défini les problèmes de compatibilité entre le langage C++ normalisé et le langage C normalisé. Ces différences ne sont pas nécessairement nombreuses, mais peuvent avoir un impact non négligeables sur le code exécuté. Par exemple, le code suivant en C :

if (sizeof('x') == sizeof(int)) {
  printf("OK!\n");
} else {
  printf("ko\n");
}

affiche non pas ko comme on pourrait s'y attendre, mais bel et bien OK! - la norme C définissant que le type d'un caractère littéral est int et non pas char comme en C++. Il y a de nombreuses autres clauses dans cette annexe (clause 3.4 : main() cannot be called recursively and cannot have its address taken ; clause 5.1 : implicit declaration of functions is not allowed ; clause 7.4 : banning implicit int ; etc), je vous encourage vraiment à lire cette annexe pour ne pas vous faire surprendre lorsque vous portez un programme C vers C++.

Bien évidemment, cette même annexe C remplis le même rôle dans le nouveau standard C++ datant de 2011. C99 étant passé par là, certaines clauses ont disparu (par exemple, clause 2.1 : C++ style comments (//) added), et d'autres ont été ajoutées (clause 5.3 : The result of a conditional expression, an assignment expression, or a comma expression may be an lvalue). Les différences sont assez importantes, et la lecture de cette annexe est de fait encouragée elle aussi - ne serait-ce que pour avoir une vision claire des différences entre le C et le C++ avant et après la mise à jour du standard.

Donc, tout est clair.

Presque.

Dans le nouveau standard C++, le rôle de cette annexe a été élargi. Etant donné que le nouveau standard C++ recycle au moins un mot clef en changeant complètement sa sémantique (auto ; en C++98, auto marque une variable dite automatique, c'est à dire qu'elle sera automatiquement détruite lorsqu'elle sortira de la portée courante ; en C++11, auto permet de déclarer une variable sans en connaître le type, dès lors que le compilateur a un moyen de déduire ce type automatiquement), il est évident qu'il existe des différences entre le C++98 et le C++11 qui peuvent provoquer un problème à la compilation. Par exemple, toujours en prenant auto en exemple :

int func() 
{
   auto int x = 0;
   ...
}

Ce code compile en C++98, mais pas en C++11.

L'idée est donc d'étendre l'annexe C avec des informations (si possibles exhaustives) sur les différences entre les deux versions du standard, la raison pour laquelle ces différences existent, et leur impact sur le code client.

A la marge ?

Si vous vous dites "je suis d'accord, mais ces différences sont nécessairement à la marge, parce qu'il y a de bonnes raisons de faire en sorte que le code existant continue de fonctionner", vous avez partiellement raison.

Partiellement, parce que quelques fois, la marge est large.

Prenons le code suivant :

#include <algorithm>
class C { private: int some_value; ... public: void swap(C& other); };
void C::swap(C& other) { std::swap(some_value, other.some_value); }

Ce code est valide en C++98. Etant relativement courant, il y a de bonnes chances qu'il reste valide en C++11. Et bien non. Et la raison n'a rien à voir avec l'ajout d'une fonctionnalité complètement folle, ou le support de tel tartempionisme intéressant. Non, la raison est toute simple : std::swap() a été déplace de <algorithm> à <utility>. Le résultat est que si vous avez 150 classes dans votre projet utilisant un tel schéma, alors vous allez devoir modifier 150 fichiers[1].

Les changements majeurs

Bien évidemment, si je dis "majeur", je pense "majeur, selon moi". Votre point de vue peut varier - si c'est le cas, je vous encourage à vous exprimer dans les commentaires, ou à m'envoyer un mail via le formulaire de contact récemment mis en ligne.

Une histoire de namespace

Si vous avez déjà pensé réaliser un travail sous un Unix quelconque, et que vous vous êtes dit "je vais encapsuler l'accès aux fonctions Posix dans un namespace particulier", il y a une bonne chance pour que vous ayez créer un namespace posix. Et bien je vous conseille de changer son nom, car ce nom de namespace est maintenant réservé pour utilisation future par le standard C++11.

Impact : changer le nom du namespace.

Concaténation de chaînes littérales

Le nouveau standard définit plusieurs nouveaux préfixes de chaînes littérales (R, u8, u8R, u, uR, U, UR, et LR). Il permet en outre de spécifier des suffixes user pour les chaînes littérales. Du coup, un code tel que celui-ci:

#define LR "Lorem "
#define _amet " amet"
#define LISDA LR"ipsum sic dolor"_amet

Ne va pas faire du tout ce que vous pensez qu'il va faire. Le préfixe LR va maintenant fixer le type des caractères, tandis que le suffixe _amet va être compris comme étant un suffixe de chaîne littérale. En C++98, la macro LISDA aurait contrenu "Lorem ipsum sic dolor amet", mais en C++11, le comportement résultant sera très, très différent.

Impact : faible ; un code bien écrit ajoute généralement au moins un espace entre deux chaînes littérales, ce qui casse non seulement la définition du préfixe et l'interprétation du suffixe. En cas de doute, ou face au comportement étrange d'un compilateur, ne pas hésiter à renommer les macros.

J'ai perdu une exception ?

La nouvelle norme ne limite pas std::new a un seul type d'exeption. Précédemment, seul std::bad_alloc pouvait être généré - ce n'est plus le cas maintenant. Si votre code ne cherche qu'à récupérer cette exception alors il devient sensible.

Impact : remplacer tous les catch (std::bad_alloc& e) par des catch (std::exception& e) est probablement la meilleure solution à court terme. Pour plus de finesse, consultez la norme C++11, qui donne les différentes exceptions pouvant être renvoyées par les différentes versions de l'opérateur unaire new.

J'ai gagné une exception ?

Le type d'erreur std::ios_base::failure, qui dérivait auparavant directement de std::exception, dérive maintenant de std::runtime_error (et donc, indirectement, de std::exception). S'il est évident qu'une clause catch (std::exception& e) continuera de permettre la récupération de cette exception, il est conseillé d'aller plus loin et de récupérer au pire std::runtime_error (std::exception devenant vraiment, vraiment très général).

Impact : aucun impact direct ; le code peut être amélioré là où std::ios_base::failure est la seule exception qui peut être générée (ou plus exactement, la seule exception qu'on veut récupérer).

Des drapeaux ios

Les types de drapeaux dans std::ios_base a changé : ce sont maintenant des masques de bits dont les valeurs sont définies comme étant des constexpr. Du coup, il n'y a plus de promotion automatique entre le type int et ces types. Par exemple, le code suivant[2] ne compilera pas :

#include <iostream>
int main() { int flag = std::ios_base::hex; std::cout.setf(flag); // ERREUR : le type de l'argument n'est pas correct return 0; }

Impact : ce changement provoque une erreur de compilation ; il est donc relativement aisé de le détecter et de le corriger. La correction la plus simple et d'utiliser le mot clef auto, même si dans ce cas précis ce n'est pas nécessairement un très bonne idée...

Un changement de taille

Les conteneurs standard existant ont subi leur lot de modifications. Le plus important de ces changements est probablement la perte par certains de ces conteneurs du membre ::size() - principalement pour respecter un autre changement : ::size() doit avoir une complexité constante, quel que soit le conteneur considéré.

Impact : sur la plupart des conteneurs ou ce membre n'avait pas une complexité constante, ::size() était principalement utilisé pour vérifier que le conteneur n'était pas vide. Il a toujours été préférable d'utiliser le membre ::empty() pour tester cette condition, et il est probable que l'idiome container.size() == 0 soit en fait relativement peu courant. Dans tous les cas, la compilation va échouer sur une erreur, et il sera trivial de corriger le code si besoin - notamment en utilisant std::count() avec un prédicat retournant toujours true si il est réellement nécessaire d'obtenir le nombre d'éléments stockés dans le conteneur.

Cette section a été supprimée ; cf. le commentaire correspondant par ptyxs (ci-dessous). Et merci à lui :)

Contenu construit par défaut

Un autre changement d'importance dans les conteneurs : tous les types utilisateurs stockés dans un conteneur doivent pouvoir être construits par défaut (c'est à dire qu'ils doivent proposer un constructeur par défaut, qu'il soit implicite ou explicite).

Impact : dans le cas où un type utilisateur utilisé dans un conteneur n'est pas default constructible, le compilateur va générer un message d'erreur - récupérer cette information n'est donc pas très complexe. Par contre, corriger le problème peut être beaucoup plus ardu. Il est par exemple relativement courant de contourner le manque de spécification de std::map<> pour travailler sur les types qui ne sont pas default constructible (notamment en n'utilisant aucune des fonctions provoquant l'appel du constructeur par défaut). Le travail de mise à jour sera ici un peu plus fin que pour les autres points cités précédemment.

Cette section a été supprimée : cf. les commentaires correspondants par Loïc Joly et Will (ci-dessous). Merci à eux !

Conclusion

Les différences dans le standard C++11 et C++98 peuvent nécessiter de revoir des parties conséquentes du code. Bien évidemment, aucun des changements à faire n'est autre que trivial - il n'en reste pas moins qu'un travail d'édition important est à prévoir, surtout dans les gros projets.

Il y a bien évidemment d'autres différences - certaines sont beaucoup moins visibles (par exemple, errno est maintenant lié à un thread, tandis qu'il était auparavant lié à un process - et donc partagé par tous les threads ; la complexité de la méthode ::size() est fixée pour tous les conteneurs la définissant, et elle est constante).

Nous avons tous bien pris l'habitude de compter sur des comportement plus ou moins définis qui ont aujourd'hui changé. En fait, il s'agit probablement de l'une des parties les plus importantes de la nouvelle version de la norme. l'ajout d'une nouvelle fonctionnalité, l'introduction d'un nouveau mot-clef, etc. sont des points qui ne peuvent pas nuire à un programme existant. Mais un changement soudain de comportement alors que le code lui-même n'a pas changé (à priori), voilà qui pose un problème.

Lorsque le changement induit par la nouvelle norme provoque une erreur de compilation alors la détection du code problématique est relativement aisé ; elle devient plus complexe lorsque le code parait compatible mais que sa sémantique change de manière subtile. Du coup, la lecture de cette annexe n'est plus, aujourd'hui, optionnelle : est est obligatoire, et ne pas vouloir prendre le temps de le faire, c'est s'exposer à des problème qui peuvent être difficiles à expliquer et à corriger.

Notes

[1] je vous conseille de vous munir d'un éditeur de ligne tel que sed, qui fera le travail à votre place

[2] tiré du texte de la norme C++11

Commentaires

1. Le jeudi, août 30 2012, 14:35 par Thomas Petit

A mon sens, la plus grosse cassure de compatibilité reste celle décrite ici :
http://stackoverflow.com/questions/...
http://stackoverflow.com/questions/...

Ce changement peut modifier radicalement le comportement d'un code, sauf qu'il est malheureusement très subtil et difficile à détecter.

2. Le lundi, septembre 3 2012, 18:14 par Idéophage

Bonjour,

Toujours rien à dire sur le fond.

le code peut être améliorer là ou > le code peut être amélioré là où

Il y a bien évidemment d'autres différences - certaines sont beaucoup moins visibles (par exemple, errno est maintenant lié à un thread, tandis qu'il était auparavant lié à un process - et donc partagé par tous les threads ; la complexité de la méthode ::size() est fixée pour tous les conteneurs la définissant, et elle est constante).

Sauf erreur (très probable), ce qui est dit ici sur size l'a déjà été plus haut (Un changement de taille).

tous les types utilisateurs stockés dans un conteneur doivent pouvoir être construits par défaut

Quelqu'un pourrait-il m'expliquer l'avantage apporté par cette contrainte ?

3. Le mardi, septembre 4 2012, 10:10 par Emmanuel Deloget

@Thomas : celle là est effectivement très, très subtile. Je suppose qu'on peut même la présenter comme un defect au comité de normalisation - à moins que ça ne résulte d'un choix conscient, auquel cas, il doit y avoir un rationale quelque part dans un des documents pré-ratification.

@Idéophage : merci pour la correction ; je vais l'appliquer tout de suite.

tous les types utilisateurs stockés dans un conteneur doivent pouvoir être construits par défaut

Quelqu'un pourrait-il m'expliquer l'avantage apporté par cette contrainte ?

Je ne vois pas réellement d'avantages ; j'y vois même des presque-inconvénients, puisqu'il était possible de passer outre cette obligation dans le précédent standard (et concrètement, ça se faisait assez régulièrement sur la manipulation de std::map ; mais c'était plus une exploitation de faille qu'autre chose, puisque ce faisant, on était tributaire de l'implémentation de std::map, ce qui n'est pas une bonne chose).

Ceci dit, il y a quand même une raison derrière ce changement : le standard est clarifié, puisqu'il émet une réserve spéciale sur cette exploitation de std::map (et sur quelques autres, plus exceptionnelle). On ne peut donc plus reprocher au standard de laisser les implémentations dans le flou (et donc de permettre le développement de deux librairies standard qui se comportent différemment, ce qui provoque des problèmes de compatibilité).

4. Le samedi, septembre 8 2012, 16:56 par ptyxs

En ce qui concerne votre premier point (taille d'un caractère et d'un int).

Sur GCC 4.7.1
@@

   std::cout << sizeof('x') << std::endl;
   std::cout << sizeof(int) << std::endl;
   if (sizeof('x') == sizeof(int)) {
     printf("OK!\n");
   } else {
     printf("ko\n");
   }

@@
retourne pourtant chez moi :
1
4
ko

Ouf...

5. Le samedi, septembre 8 2012, 17:01 par ptyxs

(complément) ... et bien sûr en utilisant l'option -std=c++11 (tout aussi bien que sans elle, bien entendu).

6. Le samedi, septembre 8 2012, 17:04 par ptyxs

Humpff... j'aurais mieux fait de me taire... vous pouvez ignorer mes deux messages précédents, vous parliez justement d'un code en C et non en C++... un peu endormi ce samedi... désolé

7. Le samedi, septembre 8 2012, 17:33 par ptyxs

Sous GCC 4.7.1 avec une ligne de commande
g-4.7 -Wall -pedantic -std=c11 essai.cpp

Le programme suivant compile sans problème :

 #include <algorithm>
class C
 {
private:
   int some_value;
public:
   C() : some_value(10) {}
   C(int i) : some_value(i) {}
   void swap(C& other);
 };
 void C::swap(C& other)
  {
   std::swap(some_value, other.some_value);
 }
int main()
{
   C a;
   C b(56);
   a.swap(b);
}
class C
{
private:
   int some_value;
public:
   C() : some_value(10) {}
   C(int i) : some_value(i) {}
   void swap(C& other);
};
void C::swap(C& other)
{
   std::swap(some_value, other.some_value);
}
int main()
{
   C a;
   C b(56);
   a.swap(b);
}

@@
avec un header algorithm, donc, et il fonctionne aussi avec le header utility.
Incorporation partielle de la norme sur ce compilateur ?

8. Le dimanche, septembre 9 2012, 10:43 par ptyxs

Je lis dans l'ouvrage de Nicolaï Josuttis sur la librairie standard, dans la seconde édition qui incorpore un traitement complet des nouveautés apportés par le C++11, ceci, p.170, chapitre 6 :
size() is provided for any container class except singly linked list (class forward_list)
Etant donné que la classe forward_list n'existait pas avant le C++11, il me semble qu'il y a contradicion directe avec ce que vous dites plus haut :
Les conteneurs standard existant ont subi leur lot de modifications. Le plus important de ces changements est probablement la perte par certains de ces conteneurs du membre ::size()
Une correction de votre texte n'apparaîtrait-elle pas nécessaire ou est-ce le Josuttis qui est fautif ?
Bien cordialement.

9. Le mardi, septembre 11 2012, 18:27 par Emmanuel Deloget

Une correction de votre texte n'apparaîtrait-elle pas nécessaire ou est-ce le Josuttis qui est fautif ?

Non, c'est moi ; mon erreur est liée à une mauvaise lecture de la norme. ::size() ne disparait pas sur les conteneurs existants. Par contre, le fait de nécessiter qu'il soit en O(1) provoque des changements - la plupart interne à la librairie.

Le standard dit :

Some container implementations that conform to C++ 2003 may not conform to the specified size() requirements in this International Standard. Adjusting containers such as std::list to the stricter requirements may require incompatible changes.

J'ai lu le texte trop vite :) Je vais corriger ces quelques lignes.

10. Le mardi, septembre 11 2012, 18:31 par Emmanuel Deloget

avec un header algorithm, donc, et il fonctionne aussi avec le header utility.

Le standard dit (emphasis is mine) :

Valid C++ 2003 code that has been compiled expecting swap to be in <algorithm> may have to instead include <utility>.

L'idée est de virer la dépendance à std::swap dans <algorithm>. Il reste possible que <algorithm> inclue <utility> pour d'autres raisons - et du coup, std::swap sera défini à l'inclusion de <algorithm>. Ca reste un comportement standard, même s'il est préférable de efaire l'inclusion de <utility> pour utiliser std::swap (au moins, on est à peu près sûr que ça va continuer à marcher).

De toute façon, il faudra encore quelques mois avant de g++ ne soit vraiment au niveau du standard. Son comportement actuel n'est donc pas vraiment significatif :)

11. Le lundi, octobre 1 2012, 01:03 par Loïc Joly

N'importe quel header a le droit d'en inclure n'importe quel autre, donc il se peut que ça marche encore avec <algorithm> (et j'imagine que ce sera le cas pour pas mal d'implémentation qui prennent soin de leur base d'utilisateurs existante), mais ce n'est plus garanti.

Pour le coup de size, ça casse surtout du code générique en fonction du conteneur :

template<class Cont> 
bool f(Cont const &c)
{
   return c.size()>10;
}

Avant, on avait la garantie que ça marche avec tous les conteneurs, plus maintenant (mais ça continue de marcher avec tous les conteneurs pour lesquels ça marchait en C++98)

Pour le coup du namespace posix, le but n'est pas que le C++ y mette quoi que ce soit, mais que si le comité posix veut définir un binding vers le C+ +, il ait un endroit pour le mettre.

Pour new (J'ai perdu une exception ?), sauf si j'ai mal vu, on peut certes retourner de nouvelles exceptions, mais celles-ci doivent toutes dériver de bad_alloc, donc pas besoin de modifier le catch.

Pour le coup du Contenu construit par défaut, là encore, je peux me tromper mais je ne vois pas dans le texte de la norme quoi que ce soit qui s'approche de près ou de lin d'une telle contrainte sur le type d'élément. Et je n'aurais pas été le seul à ne pas laisser passer ça si ça avait été proposé !

J'ai vu une contrainte de constructeur par défaut sur l'allocateur, si on construit par défaut le conteneur. Ou une contrainte si on instancie explicitement un conteneur (chose très rare, c'est lié au resize ne prenant que la taille en argument qui a besoin de ce constructeur et est instancié lors d'une instanciation explicite ), mais c'est tout !

Une autre modification, peut-être plus impactante, et liée aux move-constructors (voir par exemple http://www.open-std.org/jtc1/sc22/w... pour des exemples, même si ça date un peu, que les règles ont changé, et que je ne suis pas d'accord avec la conclusion). Là, en gros, on ne savait vraiment pas faire de manière à ce que ça marche dans tous les cas. Mais je reste persuadé que ça marche souvent ;)

Ma recommandation pour cette modification est que pour les classes ayant des invariants forts, pas de constructeur de copie, ni de déplacement, ni de destructeur, ajouter l'un de ces membres, ou alors s'assurer que l'on ne risque pas d'accéder à un moved-from object... Et une bonne règle de design pour du nouveau code est que le moved-from object doit être dans un état qui respecte ses invariants.

PS : Assez chatouilleuse, la mise en forme des commentaires...

12. Le mardi, octobre 2 2012, 14:37 par germinolegrand

Bonjour,
J'ai pour ma part été confronté à un changement assez radical dans la librairie standard, plus précisément sur std::set qui m'a obligé à modifier une grande partie de mon code : set::iterator est passé de BidirectionalIterator (until C+ + 11) à Constant bidirectional iterator (since C+ + 11), ce qui signifie qu'on ne peut modifier aucun élément d'un set. J'imagine que ce changement est dû au fait que le comité considère certainement que ceci est une mauvaise pratique. Néanmoins c'est une énorme perte de rétro-compatibilité...

P.S: oui la mise en forme des commentaires est très bizarre.

13. Le lundi, janvier 14 2013, 02:25 par koala01

Bonjour,
J'ai pour ma part été confronté à un changement assez radical dans la librairie standard, plus précisément sur std::set qui m'a obligé à modifier une grande partie de mon code : set::iterator est passé de BidirectionalIterator (until C+ + 11) à Constant bidirectional iterator (since C+ + 11), ce qui signifie qu'on ne peut modifier aucun élément d'un set. J'imagine que ce changement est dû au fait que le comité considère certainement que ceci est une mauvaise pratique. Néanmoins c'est une énorme perte de rétro-compatibilité

En fait, cela fait une éternité que je n'ai plus essayé d'utiliser des itérateurs non constant sur un set, car il me semble que Gcc (entre autres) place déjà cette restriction depuis pas mal de temps.

Mais, ceci dit, ce changement est sommes toutes logique.

En effet, le set a cette particularité que c'est l'objet contenu qui représente sa propre clé et il est pour le moins compliqué de s'assurer qu'une modification éventuelle ne toucheras pas à une des données qui participent au tri de ton set.

Pour rappel, le principe d'un std::set est qu'il doit être trié, aussi bien en précondition qu'en postcondition, à toute action qui pourrait être effectuée dessus.

A défaut de pouvoir contrôler (mais comment le pourrait-on ? ) qu'aucune modification apportée "de manière externe" à la classe ne modifie l'ordre de traitement, il n'y a aucun moyen d'assurer la validité du tri après modification d'un des éléments.

Personnellement, j'ai déjà pris depuis bien longtemps l'habitude, si je dois modifier un élément d'un std::set, de modifier une copie de l'élément et de le remplacer par cette copie ;)

14. Le mercredi, août 21 2013, 10:17 par Will

La section "Contenu construit par défaut" est entièrement fausse (je me demande où vous avez lu ça...). Il y a certes eu des changements (cf. les liens donnés par Thomas Petit), mais Loïc Joly a raison.

Exemple avec std::map (que vous citez) : http://ideone.com/nOck1I

15. Le jeudi, août 22 2013, 15:08 par Emmanuel Deloget

@Will : où j'ai lu ça :

ISO/IEC 14882:2011(E), "Information technology — Programming languages — C++", annexe C, section 2.12

C.2.12 Clause 23: containers library [diff.cpp03.containers]
23.2
Change: Requirements change: default constructible
Rationale: Clarification of container requirements.
Effect on original feature: Valid C++ 2003 code that attempts to explicitly instantiate a container using a user-defined type with no default constructor may fail to compile.

Comme quoi, je l'ai lu quand même. Je l'ai mal interprété certes, mais je l'ai lu quand même. Le simple fait que vous me posiez la question alors que je cite cette annexe en introduction en dit long.

Au delà de ça, je sais que Loïc a raison. Les erreurs d'interprétation, ça arrive (peut être pas à vous ; vous êtes tellement, tellement, tellement parfait que je me demande pourquoi je ne vous appelle pas maître).

Ceci étant dit, si (caché derrière un anonymat bien pratique) vous n'arrivez pas à quitter votre ton condescendant, je vais arrêter de vous lire (et de valider vos commentaires, par la même occasion). Je n'apprécie pas, et je n'ai pas que ça à faire, voyez vous. Ce qu'on va faire, c'est que vous allez me pointer vers vos écrits public (blog...) et je vais faire une passe de correction dessus - en utilisant un ton similaire au vôtre. Ca devrait vous plaire...

16. Le jeudi, août 22 2013, 20:06 par Will

@Emmanuel Deloget : Merci pour votre réponse. Effectivement la formulation du "Standard" peut très facilement prêter à confusion (c'est même un peu ironique puisque cette clause est censée apporter une "clarification")...

J'ai commenté pour deux raisons (autant que je me souvienne) : d'abord, j'ai été assez "choqué" en lisant la section concernée (pour avoir étudié par exemple std::vector<> en profondeur ; bon, j'admets, je suis facilement choqué) ; ensuite, alors que vous aviez réagi au commentaire de ptyxs (et modifié la section "Un changement de taille"), celui de Loïc Joly semblait "ignoré" (probablement lu, mais sans réponse ni correction de l'article).

Pour le "ton condescendant", je dois reconnaître que vous n'avez pas tort (même si je ne me suis pas senti condescendant en écrivant) ; c'est probablement en partie dû aux raisons précédentes et à la "brièveté" du commentaire (qui peut alors être ressenti comme "sec"). Mais remarquez que du coup, à moi, vous avez répondu ;) Mais je ne vous harcèlerai pas pour que vous modifiiez l'article (d'ailleurs la section n'est pas "entièrement" incorrecte, il y a bien eu une modification des spécifications, même si elle est (heureusement !) bien moindre que ce que vous aviez cru (exemple d'effet visible avec GCC : http://ideone.com/eXl1hs vs http://ideone.com/BrccqI pour std::vector<>)).
(Mais l'anonymat n'est pas seulement "pratique", je le considère un droit essentiel sur Internet. Et je ne pense pas en faire un trop mauvais usage... *pas de rire machiavélique*) (Ah, je n'ai pas d'écrits publics. Honnêtement. Pas même anonymes :) Selon la règle du 1 % je suis dans les 9 % des non-passifs mais non-créateurs. Autrement dit je ne fais que modifier ou commenter ce que d'autres ont créé. Un enquiquineur parfois, sans doute ^^ mais bien malgré moi.) (Ce commentaire devient trop long.)

Pour finir je pense que je devrais aussi vous remercier pour le reste de l'article (et le blog en général).

17. Le vendredi, août 23 2013, 11:07 par Emmanuel Deloget

Loïc n'est jamais ignoré :) Par contre, je peux manquer de temps durant une période, et du coup, ne pas avoir le temps de revoir ou modifier une portion d'un article. Le temps passe, puis j'oublie - hélas.

Je suis quelqu'un qui reconnait volontiers ses erreurs lorsqu'elles me sont pointées de manière civile. J'ai tendance à me braquer si ce n'est pas le cas. Ceci dit, maintenant que tout a été clarifier (et j'apprécie énormément que vous ayez pris le temps d'expliquer votre position), ce débat spécifique est clos.

Au niveau technique, j'ai pu voir par moi-même un certain nombre de fois que mon interprétation était incorrecte. Le paragraphe concerné a été barré et une note a été rajoutée. Au final, tout est bien qui finit bien (par contre je ne suis pas tout à fait sur qu'une clause de la norme puisse se marier et avoir beaucoup d'enfants...).

Merci :)

Ajouter un commentaire

Les commentaires peuvent être formatés en utilisant une syntaxe wiki simplifiée.

Fil des commentaires de ce billet