2009-04-19 5 views
32

Come estensione a questa domanda Are const_iterators faster?, ho un'altra domanda su const_iterators. Come rimuovere la costanza di un const_iterator? Anche se gli iteratori sono forme generalizzate di puntatori ma sono ancora const_iterator e iterator s sono due cose diverse. Quindi, credo, non posso neanche usare const_cast<> per eseguire la cover da const_iterator a iterator s.Come rimuovere constness di const_iterator?

Un approccio potrebbe essere quello di definire un iteratore che si sposta "fino all'elemento a cui si aggiungono i punti const_iterator. Ma questo sembra essere un algoritmo di tempo lineare.

Qualche idea su quale sia il modo migliore per raggiungere questo obiettivo?

+0

Stai utilizzando boost :: multi_index? – tstenner

+1

Non uso la libreria boost. –

risposta

1

È possibile sottrarre l'iteratore begin() da const_iterator per ottenere la posizione su cui si trova il const_iterator e quindi aggiungere begin() a quello per ottenere un iteratore non const. Non penso che questo sarà molto efficiente per i contenitori non lineari, ma per quelli lineari come il vettore ci vorrà del tempo costante.

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: Questo sembra funzionare solo per contenitori lineari (ad accesso casuale).

+0

Funzionerà solo se è stato definito un operatore adatto per sottrarre iteratori da iteratori const. AFAIK non esiste una cosa del genere. – PaulJWilliams

+0

Ho appena provato il codice sopra e funziona. :) – marcog

+0

Potrebbe funzionare per il vettore (Random Access iterator). Potrebbe non funzionare per la lista e altri contenitori. –

14

tempo Purtroppo lineare è l'unico modo per farlo:

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

cui iter e constIter sono typedefs adeguate e d è il contenitore su cui si effettua l'iterazione.

+5

Le implementazioni sono autorizzate a (e fanno) specializzare std :: advance e std :: distance per gli iteratori di accesso casuale in modo che questo possa essere un tempo costante per alcuni contenitori. –

+5

In realtà, questo dovrebbe essere un tempo costante per gli iteratori di accesso casuale (ben implementati). Vedi http://www.aristeia.com/Papers/CUJ_June_2001.pdf. –

+0

Per gli iteratori non a accesso casuale, penso 'iter i (d.begin()); while (ConstIter (i)! = ci) ++ i; 'sarebbe più efficiente. Ancora deludente, ma almeno cammina da "i" una volta sola. È possibile utilizzare l'invio di tag di tipo iteratore per scrivere i modelli di funzione che, in effetti, sovraccaricano il tipo di iteratore, almeno suppongono che gli iteratori siano etichettati correttamente. –

3

Questa potrebbe non essere la risposta che volevi, ma in qualche modo correlata.

Suppongo che vogliate cambiare la cosa a cui punta l'iteratore. Il modo più semplice che faccio è che const_cast invece il riferimento restituito.

Qualcosa di simile

const_cast<T&>(*it);

+0

Alcune funzioni come la cancellazione, ecc. Richiedono un const_iterator, quindi questo non funzionerebbe. – tstenner

+0

Vuoi dire che cancella richiede un iteratore non const, giusto? In questo caso, perché usi const_iterator al primo posto? la maggior parte del tempo questo tipo di cast di cui avevo bisogno era per il debug dei propers. – leiz

0

è possibile convertire il puntatore del valore di iteratore const a un puntatore non valore const e utilizzarlo direttamente qualcosa di simile

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

Questo "funziona" per 'std :: vector' e altri contenitori con memoria contigua, ma non per altri contenitori (come' std :: list'). –

2

Credo che questa conversione non è necessario in un programma ben progettato.

Se è necessario, provare a riprogettare il codice.

Come soluzione alternativa si può fare dopo:

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)); 

Ma sono penso che a volte questa conversione è impossibile, perché gli algoritmi possono avere non accedere al contenitore.

+1

+1 su refactoring. Inoltre quando si usa const_iterators è inteso come un hack delle prestazioni. –

4

Nelle risposte al tuo post precedente, c'erano un paio di persone, me incluso, che raccomandavano l'uso di costitutori invece per motivi non correlati alle prestazioni. Leggibilità, tracciabilità dalla scheda di progettazione al codice ... L'utilizzo di costitutori per fornire l'accesso di muting a un elemento non const è molto peggiore che mai con l'uso di const_iterators. Stai convertendo il tuo codice in qualcosa che solo tu capirai, con un design peggiore e un vero dolore alla manutenibilità. Usare const per buttarlo via è molto peggio che non usare affatto const.

Se sei sicuro di volerlo, la parte buona/cattiva del C++ è che puoi sempre avere abbastanza corda da impiccarti. Se la tua intenzione è utilizzare const_iterator per problemi di prestazioni, dovresti davvero ripensarlo, ma se vuoi comunque sparare a calci ... beh, il C++ può fornire la tua arma preferita.

In primo luogo, il più semplice: se le tue operazioni accettano gli argomenti come const (anche se internamente si applicano const_cast) credo che dovrebbe funzionare direttamente nella maggior parte delle implementazioni (anche se è probabilmente un comportamento indefinito).

Se non è possibile modificare i funtori, è possibile risolvere il problema da entrambi i lati: fornire un wrapper non-const iterator attorno agli iteratori const, oppure fornire un wrapper const functor attorno ai funtori non-const.

facciata Iterator, la lunga strada:

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 
}; 
3

Scott Meyer's article su preferendo iteratori oltre const_iterators risposte questo. La risposta di Visage è l'unica alternativa sicura pre-C++ 11, ma è in realtà un tempo costante per iteratori di accesso casuale ben implementati e tempo lineare per gli altri.

+1

L'articolo è standard pre-2003 (dal 2001). Mi piacerebbe vedere una revisione aggiornata dopo le modifiche del 2003 allo standard –

+0

@ DavidRodríguez-dribeas: vedere la mia risposta per una soluzione di complessità temporale costante e ben definita per C++ 11 (con tre anni di ritardo, ma meglio che mai! :-D). –

56

C'è una soluzione con complessità di tempo costante in C++ 11: per qualsiasi contenitore associativo di sequenza, associativo o non ordinato (inclusi tutti i contenitori di libreria standard), è possibile chiamare la funzione membro intervallo-cancellazione con un campo vuoto intervallo:

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

Le funzioni membro gamma-cancellazione hanno una coppia di const_iterator parametri, ma restituire un iterator. Poiché viene fornito un intervallo vuoto, la chiamata a cancellare non modifica il contenuto del contenitore.

Hat tip to Howard Hinnant and Jon Kalb for this trick.

+2

Tuttavia, è necessario accedere al contenitore. – Xeo

+7

@xeo: Beh, certo. Sarebbe un buco in piena sicurezza se si potesse fare questo senza un riferimento non const al contenitore. –

+1

+1. Ultra-pedanteria: funziona per tutti i contenitori standard, poiché tutti i contenitori standard sono sequenze o contenitori associativi o contenitori associativi non ordinati. Tuttavia, 'cancella' non fa parte dei requisiti del contenitore, quindi non deve necessariamente funzionare per tutti i tipi definiti dall'utente che soddisfano i requisiti del contenitore. Hai già detto questo nella risposta, ma aggiungi "associativo non ordinato" alla lista in Parens. Forse questa pedanteria dovrebbe essere applicata al tuo commento sulla risposta di Visage, dove hai detto "tutti i contenitori", più che sulla tua risposta completa. –

0

ho pensato che sarebbe stato divertente per trovare una soluzione a questo che funziona per i contenitori che non sono nella libreria standard e non includono il metodo di cancellazione().

Tentare di utilizzare ciò causa che Visual Studio 2013 si blocca su compile. Non sto includendo il caso di test perché lasciarlo ai lettori che riescono a capire rapidamente l'interfaccia sembra una buona idea; Non so perché questo si blocca sulla compilazione. Ciò si verifica anche quando const_iterator è uguale a 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