web-dev-qa-db-fra.com

Comment supprimer la constness de const_iterator?

En tant qu'extension à cette question const_iterators est-il plus rapide? , j'ai une autre question sur const_iterators. Comment supprimer la constance d'un const_iterator? Bien que les itérateurs soient une forme généralisée de pointeurs, néanmoins const_iterator et iterators sont deux choses différentes. Par conséquent, je crois que je ne peux pas non plus utiliser const_cast<> pour convertir de const_iterator à iterators.

Une approche consiste à définir un itérateur qui se déplace jusqu'à l'élément sur lequel const_iterator pointe. Mais cela semble être un algorithme temporel linéaire.

Une idée sur quel est le meilleur moyen d'y parvenir?

40
aJ.

Il existe une solution à complexité temporelle constante dans C++ 11: pour tout conteneur séquentiel, associatif ou non ordonné (y compris tous les conteneurs de la bibliothèque standard), vous pouvez appeler la fonction membre range-erase avec une plage vide:

template <typename Container, typename ConstIterator>
typename Container::iterator remove_constness(Container& c, ConstIterator it)
{
    return c.erase(it, it);
}

Les fonctions membres range-erase ont une paire de paramètres const_iterator, mais elles renvoient une iterator. Parce qu'une plage vide est fournie, l'appel à effacer ne modifie pas le contenu du conteneur.

Chapeau à Howard Hinnant et Jon Kalb pour cette astuce.

64
James McNellis

Malheureusement, le temps linéaire est le seul moyen de le faire:

iter i(d.begin());
advance (i,distance<ConstIter>(i,ci));

où iter et constIter sont des types appropriés et d est le conteneur sur lequel vous effectuez une itération.

14
PaulJWilliams

Dans les réponses à votre message précédent, il y avait quelques personnes, moi inclus, qui ont recommandé d'utiliser const_iterators à la place pour des raisons non liées aux performances. Lisibilité, traçabilité du tableau de conception jusqu'au code ... Utiliser const_iterators pour fournir un accès en mutation à un élément non-const est bien pire que de ne jamais utiliser const_iterators du tout. Vous convertissez votre code en quelque chose que vous seul comprendrez, dont la conception est pire et qui pose un réel problème de maintenabilité. Utiliser const pour le jeter est bien pire que de ne pas utiliser const du tout.

Si vous êtes sûr de le vouloir, le bon ou le mauvais côté de C++ est que vous pouvez toujours avoir assez de corde pour vous pendre. Si votre intention est d'utiliser const_iterator pour des problèmes de performances, vous devriez vraiment le repenser, mais si vous voulez toujours vous décoller, le C++ peut vous fournir votre arme de choix.

Tout d'abord, le plus simple: si vos opérations prennent les arguments en tant que const (même si elles appliquent const_cast en interne), je pense que cela devrait fonctionner directement dans la plupart des implémentations (même s'il s'agit probablement d'un comportement indéfini).

Si vous ne pouvez pas changer les foncteurs, vous pouvez alors résoudre le problème d'un côté ou de l'autre: fournissez un wrapper d'itérateur non-const autour des itérateurs const, ou un cache de foncteur const autour des foncteurs non-const.

La façade d'itérateur, la longue route:

template <typename T>
struct remove_const
{
    typedef T type;
};
template <typename T>
struct remove_const<const T>
{
    typedef T type;
};

template <typename T>
class unconst_iterator_type
{
    public:
        typedef std::forward_iterator_tag iterator_category;
        typedef typename remove_const<
                typename std::iterator_traits<T>::value_type
            >::type value_type;
        typedef value_type* pointer;
        typedef value_type& reference;

        unconst_iterator_type( T it )
            : it_( it ) {} // allow implicit conversions
        unconst_iterator_type& operator++() {
            ++it_;
            return *this;
        }
        value_type& operator*() {
            return const_cast<value_type&>( *it_ );
        }
        pointer operator->() {
            return const_cast<pointer>( &(*it_) );
        }
        friend bool operator==( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return lhs.it_ == rhs.it_;
        }
        friend bool operator!=( unconst_iterator_type<T> const & lhs,
                unconst_iterator_type<T> const & rhs )
        {
            return !( lhs == rhs );
        }
    private:
        T it_;  // internal (const) iterator
};

Ce n'est peut-être pas la réponse que vous vouliez, mais un peu liée.

Je suppose que vous voulez changer la chose sur laquelle l’itérateur pointe. Le moyen le plus simple que je fais est que const_cast la référence retournée à la place.

Quelque chose comme ça

const_cast<T&>(*it);

3
leiz

L'article de Scott Meyer sur la préférence des itérateurs sur const_iterators répond à cela. La réponse de Visage est la seule alternative sûre pré-C++ 11, mais il s'agit en réalité d'un temps constant pour les itérateurs à accès aléatoire correctement implémentés et d'un temps linéaire pour les autres.

3
Pontus Gagge

Je crois que cette conversion n'est pas nécessaire dans un programme bien conçu.

Si vous avez besoin de le faire, essayez de modifier le code.

Comme solution de contournement, vous pouvez faire ensuite:

typedef std::vector< size_t > container_type;
container_type v;
// filling container code 
container_type::const_iterator ci = v.begin() + 3; // set some value 
container_type::iterator i = v.begin();
std::advance( i, std::distance< container_type::const_iterator >( v.begin(), ci ) );

Mais je pense que parfois cette conversion est impossible car vos algorithmes peuvent ne pas avoir accès au conteneur.

2
bayda

Vous pouvez soustraire l'itérateur begin () de const_iterator pour obtenir la position pointée par const_iterator, puis rajouter begin () à celui-ci pour obtenir un itérateur non const. Je ne pense pas que cela sera très efficace pour les conteneurs non linéaires, mais pour les conteneurs linéaires tels que vector, cela prendra un temps constant.

vector<int> v;                                                                                                         
v.Push_back(0);
v.Push_back(1);
v.Push_back(2);
v.Push_back(3);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
vector<int>::iterator it = v.begin() + (ci - v.begin());
cout << *it << endl;
*it = 20;
cout << *ci << endl;

EDIT: Ceci ne semble fonctionner que pour les conteneurs linéaires (accès aléatoire).

1
marcog

vous pouvez convertir votre pointeur de valeur itérateur constant en un pointeur de valeur non const et l'utiliser directement comme ceci

    vector<int> v;                                                                                                         
v.Push_back(0);
v.Push_back(1);
v.Push_back(2);
v.Push_back(2);
vector<int>::const_iterator ci = v.begin() + 2;
cout << *ci << endl;
*const_cast<int*>(&(*ci)) = 7;
cout << *ci << endl;
0
Ankit

J'ai pensé qu'il serait amusant de trouver une solution à ce problème qui fonctionne pour les conteneurs ne faisant pas partie de la bibliothèque standard et n'incluant pas la méthode erase ().

Si vous tentez de l'utiliser, Visual Studio 2013 se bloque lors de la compilation. Je n'inclus pas le scénario de test car le laisser aux lecteurs, qui peuvent rapidement comprendre l'interface, semble être une bonne idée. Je ne sais pas pourquoi cela dépend de la compilation. Cela se produit même lorsque const_iterator est égal à begin ().

// deconst.h

#ifndef _miscTools_deconst
#define _miscTools_deconst

#ifdef _WIN32 
    #include <Windows.h>
#endif

namespace miscTools
{
    template < typename T >
    struct deconst
    {

        static inline typename T::iterator iterator ( typename T::const_iterator*&& target, T*&& subject )
        {
            typename T::iterator && resultant = subject->begin ( );

            bool goodItty = process < 0, T >::step ( std::move ( target ), std::move ( &resultant ), std::move ( subject ) );

        #ifdef _WIN32
             // This is just my habit with test code, and would normally be replaced by an assert
             if ( goodItty == false ) 
             {
                  OutputDebugString ( "     ERROR: deconst::iterator call. Target iterator is not within the bounds of the subject container.\n" ) 
             }
        #endif
            return std::move ( resultant );
        }

    private:

        template < std::size_t i, typename T >
        struct process
        {
            static inline bool step ( typename T::const_iterator*&& target, typename T::iterator*&& variant, T*&& subject )
            {
                if ( ( static_cast <typename T::const_iterator> ( subject->begin () + i ) ) == *target )
                {
                    ( *variant ) += i;
                    return true;
                }
                else
                {
                    if ( ( *variant + i ) < subject->end () )
                    {
                        process < ( i + 1 ), T >::step ( std::move ( target ), std::move ( variant ), std::move ( subject ) );
                    }
                    else { return false; }
                }
            }
        };
    };
}

#endif
0
user2813810