55 votes

Pourquoi mes deux tuples contenant des chaînes de caractères, créés de la même façon, ne sont pas égaux ?

Je compile le programme suivant à l'aide de Microsoft Visual C++, en tant que programme C++20 :

#include <iostream>
#include <tuple>

int main()
{
    auto t1 = std::make_tuple("one", "two", "three");
    auto t2 = std::make_tuple("one", "two", "three");

    std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
    std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;
}

Lorsque je l'exécute, je vois le résultat suivant :

(t1 == t2) is false
(t1 != t2) is true

Les tuples sont identiques, alors pourquoi les résultats de la comparaison sont-ils erronés ? Comment puis-je corriger cela ?

65voto

Yakk Points 31636

Vous comparez des pointeurs à des tampons de caractères, pas à des chaînes de caractères.

Parfois, le compilateur va transformer deux "one" dans le même tampon, parfois non.

Dans votre cas, ce n'est pas le cas. Probablement une construction de débogage.

Ajouter #include <string_view> alors

using namespace std::literals;

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);

et vous obtiendrez ce que vous attendez. (En pré c++17 les compilateurs, utilisez <string> y ""s au lieu de <string_view> y ""sv ).

37voto

Guillaume Racicot Points 1106

Quel est le type de "one" ? Il ne s'agit pas d'une chaîne de caractères, mais d'un littéral de chaîne de caractères.

Votre problème se résume essentiellement à ce code :

char const* a = "one";
char const* b = "one";

std::cout << "(a == b) is " << std::boolalpha << (a == b) << "\n";
std::cout << "(a != b) is " << std::boolalpha << (a != b) << "\n";

Ce qui donnera très probablement le même résultat.

C'est parce qu'une chaîne littérale se décomposera en un fichier char const* . La comparaison de deux pointeurs permet de comparer leur emplacement dans la mémoire. Maintenant, il s'agit de savoir si votre compilateur replie les littéraux de chaîne en un seul. Si les chaînes de caractères sont repliées, alors elles seront égales, sinon, elles ne seront pas égales. Cela peut varier en fonction des différents niveaux d'optimisation.

Comment pouvez-vous alors corriger votre comparaison ?

Utilisez de préférence std::string_view puisque vous ne semblez pas avoir besoin de posséder ou de modifier leur contenu :

using namespace std::literals;

// ... 

auto t1 = std::make_tuple("one"sv, "two"sv, "three"sv);
auto t2 = std::make_tuple("one"sv, "two"sv, "three"sv);

El std::string_view est une enveloppe mince autour d'un pointeur et d'une taille, et définit un opérateur de comparaison qui vérifie l'égalité des valeurs.

13voto

zkoza Points 1367

Le problème n'est pas lié à C++20, mais provient de la façon dont les chaînes de caractères sont implémentées. La réponse se trouve par exemple ici :

Pourquoi certains compilateurs (seulement) utilisent-ils la même adresse pour des chaînes de caractères identiques ?

En bref, votre programme tombe dans la catégorie des " indéfini comportement non spécifié", car il suppose que des chaînes de caractères identiques en C ont des adresses identiques. Ceci est dû au fait que des expressions comme "a" == "a" comparer les adresses, pas le contenu. Votre code pourrait être rendu sûr et prévisible si vous utilisiez std::string littéraux, comme "one"s , "one"sv etc., voir https://en.cppreference.com/w/cpp/string/basic_string/operator%22%22s

6voto

leftaroundabout Points 23679

auto n'est pas toujours votre ami. Je dirais que la bonne façon d'obtenir de manière fiable le "bon" comportement sans boilerplate est d'utiliser explicitement un type dont vous savez qu'il a l'égalité des valeurs. Dans ce cas, vous pouvez également omettre le make_tuple et utiliser simplement le constructeur de la liste d'initialisation :

#include <string>
#include <tuple>
#include <iostream>

typedef std::tuple<std::string, std::string, std::string> StrTriple;

int main() {

  StrTriple t1{"one", "two", "three"};
  StrTriple t2{"one", "two", "three"};

  std::cout << "(t1 == t2) is " << std::boolalpha << (t1 == t2) << "\n";
  std::cout << "(t1 != t2) is " << std::boolalpha << (t1 != t2) << "\n";

    return 0;
}

Sans doute certains diront-ils que la gestion de la mémoire de la std::string entraîne des frais généraux inutiles. string_view mai serait préférable, mais il y a de fortes chances que, dans une application réelle, les chaînes de caractères doivent de toute façon être allouées dynamiquement quelque part.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X