2010-04-12 16 views
18

Lo STL definisce comunemente un iteratore di uscita in questo modo:di iteratore di uscita

template<class Cont> 
class insert_iterator 
: public iterator<output_iterator_tag,void,void,void,void> { 
    // ... 

Perché iteratori di output definire value_type come void? Sarebbe utile per un algoritmo sapere quale tipo di valore dovrebbe produrre.

Ad esempio, una funzione che converte una query URL "key1=value1&key2=value2&key3=value3" in qualsiasi contenitore che contiene elementi di stringhe valore-chiave.

template<typename Ch,typename Tr,typename Out> 
void parse(const std::basic_string<Ch,Tr>& str, Out result) 
{ 
    std::basic_string<Ch,Tr> key, value; 
    // loop over str, parse into p ... 
     *result = typename iterator_traits<Out>::value_type(key, value); 
} 

I SGI reference page of value_type indizi per questo è perché non è possibile dereference un iteratore uscita. Ma questo non è l'unico uso di value_type: potrei voler istanziarne uno per assegnarlo all'iteratore.

Quale approccio alternativo esiste per la costruzione di un valore da emettere con l'iteratore di output? Due approcci che considerano:

  • accettare un parametro di funtore che restituire un oggetto del tipo corretto. Voglio comunque avere una versione dell'algoritmo che non accetta il parametro dell'oggetto function.
  • Richiedere che il contenitore di output detenga pair<string,string> o un tipo convertibile da quello. Mi chiedo se posso fare a meno di questo requisito, forse consentire qualsiasi elemento che può costruire da due std::string s.
+0

Quello che sto cercando di fare è generalizzare l'algoritmo in modo tale che funzioni con contenitori di qualsiasi tipo che possano costruire da due 'string's, non solo' map '. Quindi, il modello può creare un'istanza in '* risultato = coppia (chiave, valore)' così come '* risultato = unicorn_pony (chiave, valore)'. – wilhelmtell

+0

Se posso aggiungere un riferimento: un documento [pdf] sugli iteratori di output, si lamenta anche di questo problema. http://semantics.org/publications/02_02_multiout.pdf – wilhelmtell

+1

L'esempio fornito in quel documento è esattamente il motivo per cui gli iteratori di output non sono richiesti per definire un tipo di valore. Ha arbitrariamente deciso che 'MultiOut :: value_type' dovrebbe essere' double', ma in realtà * qualsiasi * tipo che può essere convertito implicitamente in 'double' e' int' sono valori ragionevoli da utilizzare per l'operatore di assegnazione. –

risposta

7

Il tipo di valore reale dell'iteratore potrebbe essere l'iteratore stesso. operator* potrebbe facilmente restituire un riferimento a *this perché il lavoro reale viene eseguito dall'operatore di assegnazione. Potreste scoprire che *it = x; e it = x; hanno esattamente lo stesso effetto con gli iteratori di output (suppongo che potrebbero essere adottate misure speciali per impedire che questi ultimi vengano compilati).

Come tale, definire il tipo di valore reale sarebbe altrettanto inutile. Definendolo un void, d'altra parte, può prevenire errori come:

typename Iter::value_type v = *it; //useless with an output iterator if it compiled 

Suppongo che questo è solo il limite del concetto di iteratori di uscita: sono oggetti che "abuso" overloading dell'operatore, in modo da appaiono come come un puntatore, mentre in realtà sta accadendo qualcosa di completamente diverso.

Il tuo problema è interessante, però.Se si desidera supportare qualsiasi contenitore, gli iteratori di output in questione potrebbero essere probabilmente std::insert_iterator, std::front_insert_iterator e std::back_insert_iterator. In questo caso si potrebbe fare qualcosa di simile al seguente:

#include <iterator> 
#include <vector> 
#include <string> 
#include <map> 
#include <iostream> 

//Iterator has value_type, use it 
template <class T, class IterValue> 
struct value_type 
{ 
    typedef IterValue type; 
}; 

//output iterator, use the container's value_type 
template <class Container> 
struct value_type<Container, void> 
{ 
    typedef typename Container::value_type type; 
}; 

template <class T, class Out> 
void parse_aux(Out out) 
{ 
    *out = typename value_type<T, typename Out::value_type>::type("a", "b"); 
} 

template <template <class> class Out, class T> 
void parse(Out<T> out) 
{ 
    parse_aux<T>(out); 
} 

//variadic template in C++0x could take care of this and other overloads that might be needed 
template <template <class, class> class Out, class T, class U> 
void parse(Out<T, U> out) 
{ 
    parse_aux<T>(out); 
} 

int main() 
{ 
    std::vector<std::pair<std::string, std::string> > vec; 
    parse(std::back_inserter(vec)); 
    std::cout << vec[0].first << ' ' << vec[0].second << '\n'; 

    std::map<std::string, std::string> map; 
    parse(std::inserter(map, map.end())); 
    std::cout << map["a"] << '\n'; 

    //just might also support normal iterators 
    std::vector<std::pair<std::string, std::string> > vec2(1); 
    parse(vec2.begin()); 
    std::cout << vec2[0].first << ' ' << vec2[0].second << '\n'; 
} 

Sarebbe ancora solo farti fino a questo punto. Suppongo che si possa prendere ulteriormente questo, quindi può anche gestire, per esempio, un std::ostream_iterator<printable_type>, ma a un certo punto sarebbe diventato così complesso che ci vuole un dio per decifrare i messaggi di errore, nel caso qualcosa vada storto.

2

Lo scopo del value_type di un iteratore è definire il tipo restituito quando l'iteratore è dereferenziato. Per gli iteratori di output, l'unico uso legittimo dell'operatore di dereferenziazione è quando viene utilizzato insieme all'operatore di assegnazione - nella forma di *output_iterator = value. Il tipo che viene restituito quando il dereferenziamento di un iteratore di output non ha necessariamente alcuna relazione diretta con i tipi che possono essere memorizzati tramite l'iteratore di output. L'unica relazione necessaria è che ci sia un modo per assegnare gli ultimi tipi al primo tipo.

Inoltre, l'iteratore di output può memorizzare valori di più tipi e questi tipi non devono avere alcuna relazione tra loro. Prendiamo ad esempio lo null_output_iterator descritto in Discarding the output of a function that needs an output iterator. Questo iteratore può accettare per memorizzare qualsiasi tipo di valore.

+0

Quando si dice _ "Lo scopo del value_type di un iteratore è definire il tipo che viene restituito quando l'iteratore è dereferenziato" _, puoi specificare il riferimento per quello? Una formulazione nello standard, forse? Nella sezione 24.3.1 dello standard non vedo dettagli su cosa sia value_type', solo che gli iteratori di output possono impostarlo su void. Nel TCPL3e, sezione 19.2.2, Stroustrup dice solo che "' value_type' è il tipo di elemento ". – wilhelmtell

+0

Dalla sezione 24.1 dello standard, "Tutti gli iteratori supportano l'espressione * i, risultante in un valore di qualche classe, enumerazione o tipo T incorporato, denominato tipo di valore dell'iteratore." Dalla sezione 24.3.1, "[...] è necessario che se Iterator è il tipo di iteratore, [...] iterator_traits :: value_type essere definito come il tipo di valore [...] dell'iteratore [ ...]. " Anche dalla sezione 24.3.1, "Nel caso di un iteratore di uscita, [...] iterator_traits :: value_type [... is ...] definito come vuoto." –

+1

Ok, so che gli iteratori di output hanno 'value_type' impostato su' void'. Con '* i' è il' value_type' di 'i', e il programmatore non ha il permesso di dereferenziare' i', c'è ancora un salto nel permettere a '* i' di essere' void'. Non è necessario "vuoto". Esistono altri usi validi per 'value_type'. Non sto discutendo di questo, semplicemente non capisco il salto da "non puoi dereferenziare un iteratore di output" a "definiamo il' value_type' di un iteratore di output a 'void'". – wilhelmtell