15 votes

Boucles for basées sur la plage pour les chaînes à terminaison nulle

J'ai supposé que les boucles for basées sur l'intervalle supporteraient les chaînes de caractères de style C.

void print_C_str(const char* str)
{
    for(char c : str)
    {
        cout << c;
    }
}

Cependant, ce n'est pas le cas, la norme [stmt.ranged] (6.5.4) dit que le système "range-based-for" fonctionne dans l'une des 3 possibilités suivantes :

  1. L'intervalle est un tableau
  2. La gamme est une classe avec un appelable begin y end méthode
  3. Il existe un ADL accessible dans un espace de noms associé (plus le std espace de noms)

Quand j'ajoute begin y end fonctions pour const char* dans l'espace de nom global, je reçois toujours des erreurs (à la fois de VS12 et de GCC 4.7).

Existe-t-il un moyen de faire fonctionner les boucles for basées sur l'intervalle avec les chaînes de style C ?

J'ai essayé d'ajouter une surcharge à namespace std et cela a fonctionné mais, d'après ce que j'ai compris, il est illégal d'ajouter des surcharges à la fonction namespace std (est-ce correct ?)

22voto

R. Martinho Fernandes Points 96873

Si vous écrivez un itérateur trivial pour les chaînes de caractères à terminaison nulle, vous pouvez le faire en appelant une fonction sur le pointeur qui renvoie une plage spéciale, au lieu de traiter le pointeur lui-même comme la plage.

template <typename Char>
struct null_terminated_range_iterator {
public:
    // make an end iterator
    null_terminated_range_iterator() : ptr(nullptr) {}
    // make a non-end iterator (well, unless you pass nullptr ;)
    null_terminated_range_iterator(Char* ptr) : ptr(ptr) {}

    // blah blah trivial iterator stuff that delegates to the ptr

    bool operator==(null_terminated_range_iterator const& that) const {
        // iterators are equal if they point to the same location
        return ptr == that.ptr
            // or if they are both end iterators
            || is_end() && that.is_end();
    }

private:
    bool is_end() {
        // end iterators can be created by the default ctor
        return !ptr
            // or by advancing until a null character
            || !*ptr;
    }

    Char* ptr;
}

template <typename Char>
using null_terminated_range = boost::iterator_range<null_terminated_range_iterator<Char>>;
// ... or any other class that aggregates two iterators
// to provide them as begin() and end()

// turn a pointer into a null-terminated range
template <typename Char>
null_terminated_range<Char> null_terminated_string(Char* str) {
    return null_terminated_range<Char>(str, {});
}

Et l'usage ressemble à ça :

for(char c : null_terminated_string(str))
{
    cout << c;
}

Je ne pense pas que cela perde en expressivité. En fait, je pense que celle-ci est plus claire.

4voto

Motti Points 32921

Une solution possible consiste à envelopper la chaîne à terminaison nulle dans un autre type. L'implémentation la plus simple est la suivante (elle est moins performante que la méthode suivante R. Martinho Fernandes puisqu'il appelle strlen mais c'est aussi beaucoup moins de code).

class null_terminated_range {
    const char* p:
public:
    null_terminated_range(const char* p) : p(p) {}
    const char * begin() const { return p; }
    const char * end() const { return p + strlen(p); }
};

Utilisation :

for(char c : null_terminated_range(str) )

2voto

Puppy Points 90818

Une chaîne de caractères C n'est pas un tableau, ce n'est pas une classe qui a begin / end et vous ne trouverez rien d'ADL parce que l'argument est un primitif. On peut argumenter que cela devrait être une simple recherche non qualifiée, avec ADL, qui serait trouver une fonction dans l'espace de nom global. Mais, compte tenu de la formulation, je pense que ce n'est pas possible.

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