C++ moderne - utilisation simultanée de std::vector et std::string

Les ingénieurs tout frais sortis de l'école se retrouvent en général face à la dure réalité de l'industrie : écrire du code fiable et donc hyper-testé. Les outils de test et de couverture de code font assez rarement partie de leur bagage, mais on se charge très vite de les remettre dans le droit chemin.

Merci @J-M-L pour vos explications.
Les foncteurs sont au programme dans mon livre. Pour l'instant, je sais qu'il s'agit d'un objet qui agit comme une fonction mais j'en connaîtrais les détails plus tard selon la chronologie déterminée par l'auteur.

En fait, je ne connais pas du tout le monde de l'industrie et j'imagine qu'il fixe des règles basées sur l'efficacité ! Je me rappelle de @biggil qui évoquait les difficultés de la programmation basée bien évidemment sur l'efficacité mais aussi sur la sécurité avec laquelle on ne rigole pas (il me semble qu'il évoquait la programmation concernant la fermeture des portes du métro).
En fait c'était un coup de gueule basé sur l'émotion et non pas sur des réalités que je ne connais pas.

pour vous donner une vision plus claire de ce que fait auto et la lambda dans votre code

  auto lambda =  [](int  const & parametre) -> void
    {
     
      Serial.print( "message reçu : ")  ;
      Serial.println( parametre);
    };

C'est comme si vous aviez une petite classe qui fait cela

class lambdaSimul {
  public:
    void operator()(int  const & parametre) {
      Serial.print( "message reçu : ")  ;
      Serial.println( parametre);
    }
};

générée de manière cachée pour vous, on a créé un nouveau type que j'ai appelé ici lambdaSimul

et votre code ferait

void setup() {
  Serial.begin(115200);
  lambdaSimul myLambda;
  myLambda(405);
}

void loop() {}

donc on peut tester avec

class lambdaSimul {
  public:
    void operator()(int  const & parametre) {
      Serial.print( "message reçu : ")  ;
      Serial.println( parametre);
    }
};

void setup() {
  Serial.begin(115200);
  lambdaSimul myLambda;
  myLambda(405);
}

void loop() {}

J'aime bien que le langage soit un peu strict.
J'ai travaillé pendant des années sur des programmes en Fortran (j'ai 66 balais)
En (vieux) fortran, on ne déclare pas les variables. C'est cool !
Ouais, sauf que si quelque part je fais une faute de frappe dans le nom d'une variable (ça arrive), et bien le compilateur juge que c'est une nouvelle variable, au mieux initialisée à 0.
Très, très sympa pour retrouver l'erreur (en lisant le code à vitesse "normale", on ne voit pas la faute de frappe).
Avec en plus mon chef de service qui ne comprend pas qu'on crée des noms de variable de plus de 2 caractères, c'est trop long à taper :slight_smile:

Alors pour moi, auto c'est sympa pour m'éviter de taper MaClasseAuNomBienTropLong<EtEnPlusTemplate>::iterator
mais pas pour remplacer int ou char* !

Très intéressant
Merci @J-M-L

Par le grand Zéro et son prophète le Un ! Que les dieux de l'informatique nous préservent de cette catastrophe !!!

Cela dit c’est facile d’être expert en programmation graphique quand on comprend ce qu’il se passe sous le capot…

J'ai cru comprendre qu'une nouvelle normalisation du C++ aura lieu en 2023. J'espère que la tendance ne sera pas trop à la simplification.
Bon ceci dit, je retiens que l'utilisation de auto facilite la maintenance du code.
En fait ce qui me déplait c'est par exemple les trop nombreux algorithmes qui simplifient trop le code.
J'espère ne pas choquer mais en quelque sorte c'est un peu comme sur Arduino : n'importe qui peut faire de l'électronique car les librairies assurent la gestion du matériel à la place de l'utilisateur ! Par exemple n'importe qui peut créer une télécommande pour sa télévision sans même en connaître le principe de fonctionnement. En fait c'est ce qui fait le succès d'Arduino !
C'est pour cette raison que j'essaye d'aller plus loin dans la compréhension des langages. Je dis bien j'essaye car j'ai mes limites et il va falloir que j'en face de même pour le matériel !

Voilà avoir des animateurs comme @J-M-L sur le forum, c'est une chance inestimable ! pareil pour @68tjs, @al1fch, @hbachetti ! et tous les autres que j'ai déjà cités hier ...
J'espère que les animateurs de demain auront toujours les compétences de ceux d'aujourd'hui ...

Bonne journée.

auto n’est pas vraiment une simplification.

par exemple dans un range for loop vous pouvez faire

for (auto x : container) {…}

ou

for (auto & x : container) {…}

ou

for (auto && x : container) {…}

Il faut comprendre la différence

La tendance c’est plutôt vers un typage plus fort.

Bonjour @J-M-L
Autant, je comprends bien les deux premiers formats, autant le dernier je ne le comprends pas.

C’est pour ça que je dis que ce n’est pas si simple :slight_smile: il faut comprendre les lvalue et rvalue

Lisez ces 2 liens

https://en.cppreference.com/w/cpp/language/reference

@J-M-L

  • Avec auto&, on parcourt un conteneur par l'intermédiaire des adresses mémoires de ses éléments constitutifs;
  • Avec auto&&, on parcourt un conteneur par l'intermédiaire de la valeur des données contenues à une adresse mémoire de ses éléments constitutifs;

??

Bon ma réponse ne me satisfait pas, je vais courir. J'ai des invités à midi.
Je vais réfléchir à tout ça. Je comprends que j'ai encore beaucoup à apprendre ...

Dans le for loop range avec auto && variable on s'autorise à parcourir un container dont l'itérateur ne donnerait pas une lvalue (dont on pourrait prendre la référence)

il faut passer un peu de temps à comprendre les lvalue et rvalue et autres catégories de valeur.

En gros - chaque expression C++ a un type - ça vous connaissez - mais elles appartiennent aussi à une catégorie de valeur (category value en anglais) qui définissent les règles que les compilateurs suivent à la création, la copie ou lors du déplacement d’objets temporaires pendant l’évaluation de l’expression

On trouvera comme catégorie : glvalue, prvalue, xvalue et les notions les plus mentionnées les lvalue et rvalue

Si vous lisez la littérature on vous dira qu'une expression peut être soit une glvalue soit une rvalue et qu'une glvalue peut être une lvalue ou une xvalue alors qu'une rvalue peut être une xvalue ou une prvalue... :cold_face: :scream:

je vous laisse lire de la littérature sur tout cela mais voici un petit exemple qui illustre le challenge des références

Imaginez que vous voulez passer une référence sur un entier à une fonction pour le multiplier par deux

int fois2(int & valeur) {
  return valeur * 2;
}

si vous écrivez ce code

int fois2(int & valeur) {
  Serial.print("fois2 avec référence -> ");
  return valeur * 2;
}

void setup() {
  Serial.begin(115200);
  int y = 42;
  Serial.println(fois2(y));
}

void loop() {}

tout va fonctionner et vous verrez dans la console
fois2 avec référence -> 84

mais si vous écrivez

int fois2(int & valeur) {
  Serial.print("fois2 avec référence -> ");
  return valeur * 2;
}

void setup() {
  Serial.begin(115200);
  Serial.println(fois2(42));
}

void loop() {}

le compilateur va se plaindre en disant

error: cannot bind non-const lvalue reference of type 'int&' to an rvalue of type 'int'
   Serial.println(fois2(42));
                        ^

parce que 42 n'a pas d'adresse mémoire, c'est une rvalue et donc on ne peut pas passer sa référence

c'est la que la référence universelle vient vous aider. Si vous écrivez

int fois2(int && valeur) {
  Serial.print("fois2 avec référence universelle -> ");
  return valeur * 2;
}

void setup() {
  Serial.begin(115200);
  Serial.println(fois2(42));
}

void loop() {}

vous verrez que ça compile et la console affiche

fois2 avec référence universelle -> 84

le compilateur s'est débrouillé pour trouver quand même la référence à ce 42

mais ce coup ci, si vous essayez

int fois2(int && valeur) {
  Serial.print("fois2 avec référence universelle -> ");
  return valeur * 2;
}

void setup() {
  Serial.begin(115200);
  int y = 42;
  Serial.println(fois2(y));
}

void loop() {}

alors le compilateur va se plaindre...

error: cannot bind rvalue reference of type 'int&&' to lvalue of type 'int'
   Serial.println(fois2(y));
                         ^

➜ pour résoudre cela avec C++11 il faut donc utiliser la surcharge de fonction et écrire

int fois2(int & valeur) {
  Serial.print("fois2 avec référence simple -> ");
  return valeur * 2;
}

int fois2(int && valeur) {
  Serial.print("fois2 avec référence universelle -> ");
  return valeur * 2;
}

void setup() {
  Serial.begin(115200);
  int y = 42;
  Serial.println(fois2(y));
  Serial.println(fois2(42));
}

void loop() {}

le premier appel à fois2 utilise la référence simple alors que pour le second le compilateur a reconnu une rvalue et va donc appeler la seconde fonction. On voit dans la console

fois2 avec référence simple -> 84
fois2 avec référence universelle -> 84

je ne sais pas si ça aide :slight_smile:

@J-M-L ,

Mes invités viennent de partir, je lis votre message et c'est très clair !
Pour aider, c'est sûr que ça aide, j'ai parfaitement compris !
Vous devriez écrire un livre. Dans mes bouquins c'est moins facile à comprendre !

Merci.

Revenons à la question initiale :

Pour uniquement visualiser les éléments d'un conteneur dans le moniteur série, un passage par valeur suffit (un passage par référence fonctionne également) :

#include<vector>
using namespace std;
vector<int> v = {1, 3, 5, 7, 9};


void setup() {
  Serial.begin(115200);

  for (auto x : v) {
    Serial.print(x); Serial.print(" ");

  }
}

void loop() {}

Pour modifier les éléments du conteneur, il faudra un passage par référence et c'est bien normal :
Dans cet exemple le passage par valeur ne modifie rien :

#include<vector>
using namespace std;
vector<int> v = {1, 3, 5, 7, 9};


void setup() {
  Serial.begin(115200);
  for (auto x : v)   x += 10;      
 
  for (auto x : v) {
    Serial.print(x); Serial.print(" ");

  }
}

void loop() {}

Ici le passage par référence est efficace et permet de modifier les éléments du conteneur :

#include<vector>
using namespace std;
vector<int> v = {1, 3, 5, 7, 9};


void setup() {
  Serial.begin(115200);
  for (auto & x : v)   x += 10;      
 
  for (auto & x : v) {
    Serial.print(x); Serial.print(" ");

  }
}

void loop() {}

Enfin supposons qu'on souhaite inverser un état booléen, ii est impossible de renvoyer une référence sur un état booléen, on utilise donc un proxy iterator :

#include<vector>
using namespace std;
vector<bool> v = {true, false, false, true};

void setup() {
Serial.begin(115200);
for (auto&& x : v)  
    x = !x;
for (const auto& x : v){
   Serial.print(x);
   Serial.print(" ");
}
  
}

void loop() {}

Voilà, les choses sont plus complexes que cela encore mais il faudrait que j'en termine avec la POO, les classes pour en savoir plus.
Mais je suis preneur pour toutes informations supplémentaires, de toutes façon avec vous je comprends facilement.

Bonne soirée.

Pourquoi tu ne pourrais pas renvoyer une référence sur un type bool ?
Tu parle d'une fonction avec ce prototypage ?
bool* alternate()

Bonsoir @terwal

Essaye

for (auto& x : v)  
    x = !x;

Tu auras un message d'erreur. Pas de compilation.

Avec

for (auto&& x : v)  
x = !x;

Pas de problème.

@terwal
En fait avec les vector<bool> les bool sont stockés sous forme de bits dans des entiers condenssés plutôt que sous forme de bool réels, ses itérateurs ne peuvent pas renvoyer de bool réels lorsqu'ils sont déréférencés.