38 votes

Incohérence entre les littéraux std::string et string

J'ai découvert une incohérence troublante entre std::string et les littéraux de chaîne en C++0x :

#include <iostream>
#include <string>

int main()
{
    int i = 0;
    for (auto e : "hello")
        ++i;
    std::cout << "Number of elements: " << i << '\n';

    i = 0;
    for (auto e : std::string("hello"))
        ++i;
    std::cout << "Number of elements: " << i << '\n';

    return 0;
}

La sortie est :

Number of elements: 6
Number of elements: 5

Je comprends pourquoi cela se produit : la chaîne littérale est en fait un tableau de caractères qui inclut le caractère nul, et lorsque la boucle for basée sur l'intervalle appelle std::end() sur le tableau de caractères, il obtient un pointeur au-delà de la fin du tableau ; puisque le caractère nul fait partie du tableau, il obtient donc un pointeur au-delà du caractère nul.

Cependant, je pense que cela est très peu souhaitable : sûrement std::string et les chaînes de caractères doivent se comporter de la même manière lorsqu'il s'agit de propriétés aussi élémentaires que leur longueur ?

Existe-t-il un moyen de résoudre cette incohérence ? Par exemple, est-ce que std::begin() y std::end() soit surchargé pour les tableaux de caractères de façon à ce que la plage qu'ils délimitent n'inclue pas le caractère nul de fin ? Dans l'affirmative, pourquoi cela n'a-t-il pas été fait ?

EDIT : Pour justifier un peu plus mon indignation à ceux qui ont dit que je ne fais que subir les conséquences de l'utilisation de chaînes de style C qui sont une "fonctionnalité héritée", considérez un code comme le suivant :

template <typename Range>
void f(Range&& r)
{
    for (auto e : r)
    {
        ...
    }
}

Vous attendriez-vous f("hello") y f(std::string("hello")) de faire quelque chose de différent ?

29voto

Howard Hinnant Points 59526

Si nous avons surchargé std::begin() y std::end() pour les tableaux de const chars pour retourner un moins que la taille du tableau, alors le code suivant produirait 4 au lieu du 5 attendu :

#include <iostream>

int main()
{
    const char s[5] = {'h', 'e', 'l', 'l', 'o'};
    int i = 0;
    for (auto e : s)
        ++i;
    std::cout << "Number of elements: " << i << '\n';
}

21voto

David Hammen Points 17912

Cependant, je pense que cela n'est pas souhaitable : les chaînes de caractères std::string et les chaînes de caractères littéraux devraient se comporter de la même manière lorsqu'il s'agit de propriétés aussi basiques que leur longueur ?

Les chaînes littérales ont par définition un caractère nul (caché) à la fin de la chaîne. Les chaînes de caractères std::strings n'en ont pas. Comme les chaînes std::strings ont une longueur, ce caractère nul est un peu superflu. La section standard de la bibliothèque des chaînes de caractères autorise explicitement les chaînes de caractères à terminaison non nulle.

Modifier
Je ne pense pas avoir jamais donné une réponse plus controversée dans le sens d'une énorme quantité de votes positifs et négatifs.

Le site auto lorsqu'il est appliqué à un tableau de style C, il itère sur chaque élément du tableau. La détermination de l'intervalle est faite au moment de la compilation, et non de l'exécution. Ceci est mal formulé, par exemple :

char * str;
for (auto c : str) {
   do_something_with (c);
}

Certaines personnes utilisent des tableaux de type char pour contenir des données arbitraires. Oui, c'est une façon de penser à l'ancienne du C, et peut-être auraient-ils dû utiliser un tableau de type C++, mais la construction est tout à fait valide et utile. Ces personnes seraient plutôt contrariées si leur itérateur automatique sur un tableau de type char buffer[1024]; s'arrête à l'élément 15 juste parce que cet élément a la même valeur que le caractère nul. Un itérateur automatique sur un Type buffer[1024]; se déroulera jusqu'à la fin. Qu'est-ce qui rend un tableau de chars si digne d'une implémentation complètement différente ?

Notez que si vous souhaitez que l'itérateur automatique sur un tableau de caractères s'arrête plus tôt, il existe un mécanisme simple pour le faire : Ajoutez un if (c == '0') break; dans le corps de votre boucle.

La ligne du bas : Il n'y a pas d'incohérence ici. Le site auto sur un tableau de char[] est cohérente avec la façon dont l'itérateur automatique fonctionne sur tout autre tableau de style C.

19voto

Que vous obteniez 6 dans le premier cas est une fuite d'abstraction qui ne pouvait pas être évitée en C. std::string "répare" cela. Pour des raisons de compatibilité, le comportement des chaînes de caractères de style C ne change pas en C++.

Par exemple, peut-on surcharger std::begin() et std::end() pour les tableaux de pour les tableaux de caractères de façon à ce que la plage qu'ils délimitent n'inclue pas l'élément caractère nul de fin de tableau ? Si oui, pourquoi cela n'a-t-il pas été fait ?

En supposant que l'accès se fasse par un pointeur (par opposition à char[N] ), seulement en incorporant une variable à l'intérieur de la chaîne contenant le nombre de caractères, de sorte que la recherche de NULL n'est plus nécessaire. Oups ! C'est std::string .

La façon de "résoudre l'incohérence" est de ne pas utiliser du tout les anciennes fonctionnalités .

6voto

Ise Wisteria Points 5852

Selon la norme N3290 6.5.4, si la plage est un tableau, les valeurs limites sont initialisées automatiquement sans begin / end fonction d'envoi.
Alors, pourquoi ne pas préparer un wrapper comme le suivant ?

struct literal_t {
    char const *b, *e;
    literal_t( char const* b, char const* e ) : b( b ), e( e ) {}
    char const* begin() const { return b; }
    char const* end  () const { return e; }
};

template< int N >
literal_t literal( char const (&a)[N] ) {
    return literal_t( a, a + N - 1 );
};

Alors le code suivant sera valide :

for (auto e : literal("hello")) ...

Si votre compilateur fournit un littéral défini par l'utilisateur, il peut être utile de l'abréger :

literal operator"" _l( char const* p, std::size_t l ) {
    return literal_t( p, p + l ); // l excludes '\0'
}

for (auto e : "hello"_l) ...

EDIT : Les éléments suivants auront des frais généraux moins élevés (les littéraux définis par l'utilisateur ne seront cependant pas disponibles).

template< size_t N >
char const (&literal( char const (&x)[ N ] ))[ N - 1 ] {
    return (char const(&)[ N - 1 ]) x;
}

for (auto e : literal("hello")) ...

4voto

robert Points 10493

Si vous voulez la longueur, vous devez utiliser strlen() pour la chaîne C et .length() pour la chaîne C++. Vous ne pouvez pas traiter les chaînes C et les chaînes C++ de la même manière - elles ont un comportement différent.

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