2013-05-11 10 views
8

Ho due domande e mezzo strettamente correlate. Dato un tipo di iteratore STL passato come parametro del modello:Verifica/modifica iteratore "costanza"

  1. Come determinare se il tipo corrisponde a un iteratore const o non const?
  2. In alternativa a 1., come imporre (utilizzando enable_if s per esempio) che questo tipo corrisponde a un iteratore non const?
  3. Come ottenere la versione costante dell'iteratore da quello non costante (e vice versa)? [Nota: risposta in this post; non a caso, non puoi. ]

Da dove viene questa domanda viene da:

Ho scritto una piccola classe per facilitare aritmetiche/operatori relazionali/algebriche su vettori (dal vettore intendo dati di dimensione fissa 1D, non il Vettori STL). Invece di imporre un contenitore di dati specifico, ho definito un'interfaccia e derivato diversi contenitori possibili che fondamentalmente "avvolgono" vari modi di memorizzazione dei dati. Uno di questi contenitori è un wrapper per gli iteratori di STL e ho qualche problema con esso.

+0

Si potrebbe iniziare con ['std :: is_const '] (http://en.cppreference.com/w/cpp/types/is_const). – BoBTFish

+1

@BoBTFish Ho provato ma non funziona. 'const_iterator' e' const iterator' sono due cose diverse. Prova questo: http://pastebin.com/kKQEQthj – Sheljohn

+0

@BoBTFish Spiacente, hai modificato il tuo commento? Potrei giurare che non ho visto il 'decltype (* it)' quando ho postato il mio ultimo commento .. Avresti ragione anch'io con questo suppongo :) – Sheljohn

risposta

6

Domanda 1:

è possibile utilizzare il seguente tipo caratteristica:

template<typename T, typename = void> 
struct is_const_iterator : std::false_type { }; 

template<typename T> 
struct is_const_iterator<T, 
    typename std::enable_if< 
     std::is_const< 
      typename std::remove_pointer< 
       typename std::iterator_traits<T>::pointer 
       >::type 
      >::value 
     >::type> : std::true_type { }; 

Ecco una dimostrazione:

#include <type_traits> 
#include <iterator> 
#include <list> 
#include <vector> 

template<typename T, typename = void> 
struct is_const_iterator : std::false_type { }; 

template<typename T> 
struct is_const_iterator<T, 
    typename std::enable_if< 
     std::is_const< 
      typename std::remove_pointer< 
       typename std::iterator_traits<T>::pointer 
       >::type 
      >::value 
     >::type> : std::true_type { }; 

int main() 
{ 
    typedef std::list<int>::iterator LI; 
    typedef std::list<int>::const_iterator CLI; 
    static_assert(is_const_iterator<LI>::value, "!"); // Fires 
    static_assert(is_const_iterator<CLI>::value, "!"); // Does not fire 

    typedef std::vector<int>::iterator VI; 
    typedef std::vector<int>::const_iterator CVI; 
    static_assert(is_const_iterator<VI>::value, "!"); // Fires 
    static_assert(is_const_iterator<CVI>::value, "!"); // Does not fire 
} 

Ed ecco un live example.

Domanda 2:

Con il tipo di tratto sopra, questo diventa semplice. Supponiamo di avere un modello di funzione foo() che si desidera vincolare in modo che accetti solo non const iteratori:

template<typename It, 
    typename std::enable_if<!is_const_iterator<It>::value>::type* = nullptr> 
void foo(It i) 
{ 
    // Does something with i... 
} 

E un semplice programma dimostrativo:

int main() 
{ 
    std::vector<int> v; 
    foo(v.begin()); // OK 
    foo(v.cbegin()); // ERROR! 
} 

Ed ecco un live example.

+0

Ho solo pensato di usare il typedef ':: reference' anche dai caratteri type .. Ma la tua risposta è impeccabile! Grazie :) – Sheljohn

+0

@ Sh3ljohn: Sono contento che mi abbia aiutato :) –

+0

Solo una domanda: perché non usare 'typename = typename std :: enable_if :: value> :: type' come" filtro modello "nel tuo esempio per la domanda 2? Questo è il modo di usare enable_ifs che ho visto più frequentemente, ma se c'è qualcosa di sbagliato in questo, mi piacerebbe sapere cosa :) – Sheljohn

3

Per 1), si potrebbe fare qualcosa di simile:

std::is_const< 
    typename std::remove_reference< 
    typename std::iterator_traits<Iterator>::reference 
    >::type 
>::value 

O questo:

std::is_const< 
    typename std::remove_reference< 
    decltype(*iterator) 
    >::type 
>::value 

È possibile utilizzare questi predicati da passare al std::enable_if per implementare 2).

NOTA: Come sottolineato da R. Martinho Fernandes nei commenti, questi predicati falliranno se l'iteratore in questione utilizza un diverso tipo di riferimenti semplici per la sua caratteristica reference (come std::vector<bool>::const_iterator fa).

+0

Grazie mille! Immagino che tu sia stato il primo a pubblicare la risposta, e @AndyProwl ha la stessa risposta di te, quindi non so quale contrassegnare .. Solo una domanda: perché usare 'decltype'? – Sheljohn

+1

@ Sh3ljohn '* iterator' è un'espressione *, * ma' std :: remove_reference' richiede un * tipo * (poiché è un modello con un parametro di tipo template). Quindi, in qualche modo devi ottenere il tipo di espressione - e questo è ciò che è "decltype". – Angew

+0

Cosa succede se 'decltype (* iterator)' è un proxy e non un riferimento? –

2

Si potrebbe utilizzare SFINAE su

decltype(**(T*)0 = std::move(**(T*)0)) 

o (la preferenza di Xeo)

decltype(*declval<T&>() = std::move(*declval<T&>())) 

che verifica se dereferencing l'iteratore ti dà qualcosa assegnabile. Non perfetto, se il tipo di elemento della raccolta non è assegnabile, ma a che punto sarebbe comunque avere un valore non const_iterator?

Non eseguire il test per const_iterator, verificare l'operazione effettivamente necessaria al proprio algoritmo.

+2

Per amore di tutto, * usa '* std :: declval ()' *. – Xeo

+0

@ Xeo: perché? Sono circa 20 personaggi extra per quale beneficio? –

+0

Leggibilità ...? – 0x499602D2