2010-04-12 22 views
18

El STL define comúnmente un iterador de salida de este modo:de iterador de salida

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

¿Por qué definir iteradores salida value_type como void? Sería útil para un algoritmo saber qué tipo de valor se supone que debe generar.

Por ejemplo, una función que traduce una consulta de URL "key1=value1&key2=value2&key3=value3" en cualquier contenedor que contiene elementos de cadenas de valor-clave.

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

Los consejos SGI reference page of value_type esto se debe a que no es posible eliminar la referencia de un iterador de salida. Pero ese no es el único uso de value_type: podría querer crear una instancia para asignarlo al iterador.

¿Qué enfoque alternativo existe para construir un valor a la salida con el iterador de salida? Dos enfoques que consideran:

  • aceptar un parámetro funtor que devolvería un objeto del tipo correcto. Todavía quiero tener una versión del algoritmo que no tome ese parámetro de objeto de función.
  • Requerir que el contenedor de salida tenga pair<string,string>, o bien un tipo convertible de ese. Me pregunto si puedo prescindir de este requisito, quizás permitir cualquier elemento que pueda construir a partir de dos std::string s.
+0

Lo que trato de hacer es generalizar el algoritmo de manera que funcione con contenedores de cualquier tipo que puedan construir a partir de dos 'cadenas ', no solo' map '. Entonces, la plantilla puede crear instancias en '* result = pair (key, value)' así como en '* result = unicorn_pony (key, value)'. – wilhelmtell

+0

Si puedo agregar una referencia: un documento [pdf] sobre los iteradores de salida, también se queja de este problema. http://semantics.org/publications/02_02_multiout.pdf – wilhelmtell

+1

El ejemplo dado en ese documento es exactamente por qué los iteradores de salida no son necesarios para definir un tipo de valor. Decidió arbitrariamente que 'MultiOut :: value_type' debería ser' double', pero en realidad * cualquier tipo * que se pueda convertir implícitamente a 'double' y' int' son valores razonables para usar para el operador de asignación. –

Respuesta

7

El tipo de valor real del iterador bien podría ser el propio iterador. operator* puede simplemente devolver una referencia a *this porque el trabajo real es realizado por el operador de asignación. Puede encontrar que *it = x; y it = x; tienen exactamente el mismo efecto que los iteradores de salida (supongo que se pueden tomar medidas especiales para evitar que este último se compile).

Como tal, definir el tipo de valor real sería tan inútil. Definiéndola como void, por el contrario, puede prevenir errores como:

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

supongo que esto es sólo el límite del concepto de iteradores de salida: son objetos que la sobrecarga de operadores "abuso", de manera que se aparece puntero, mientras que en realidad algo completamente diferente está sucediendo.

Tu problema es interesante, sin embargo.Si desea admitir cualquier contenedor, entonces los iteradores de salida en cuestión probablemente serían std::insert_iterator, std::front_insert_iterator y std::back_insert_iterator. En este caso, podría hacer algo como lo siguiente:

#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'; 
} 

Todavía podría llegar tan lejos. Supongo que uno podría llevar esto más allá, por lo que también puede administrar, por ejemplo, un std::ostream_iterator<printable_type>, pero en algún momento sería tan complejo que se necesita un dios para descifrar los mensajes de error, si algo sale mal.

2

El propósito del valor_type de un iterador es definir el tipo que se devuelve cuando se quita la referencia de ese iterador. Para los iteradores de salida, el único uso legítimo del operador de desreferencia es cuando se utiliza junto con el operador de asignación, en la forma de *output_iterator = value. El tipo que se devuelve al desreferenciar un iterador de salida no tiene necesariamente ninguna relación directa con los tipos que se pueden almacenar a través del iterador de salida. La única relación necesaria es que haya alguna forma de asignar los últimos tipos al primer tipo.

Además, el iterador de salida puede almacenar valores de varios tipos, y estos tipos no tienen que tener ninguna relación entre sí. Tomemos como ejemplo el null_output_iterator descrito en Discarding the output of a function that needs an output iterator. Ese iterador puede aceptar para el almacenamiento cualquier tipo de valor.

+0

Cuando dices _ "El propósito del valor_tipo de un iterador es definir el tipo que se devuelve cuando se elimina la referencia de ese iterador" _, ¿puedes especificar la referencia para eso? Una redacción en el estándar, tal vez? En la sección 24.3.1 del estándar, no veo detalles sobre para qué es 'value_type', solo que los iteradores de salida pueden establecerlo como vacío. En el TCPL3e, sección 19.2.2, Stroustrup solo dice que "' value_type' es el tipo de elemento ". – wilhelmtell

+0

De la sección 24.1 del estándar, "Todos los iteradores soporto la expresión * i, lo que da como resultado un valor de alguna clase, enumeración o tipo incorporado T, llamado el tipo de valor del iterador". De la sección 24.3.1, "[...] se requiere que si Iterator es el tipo de un iterador, [...] iterator_traits :: value_type se define como el tipo de [...] valor del iterador [ ...]. " También desde la sección 24.3.1, "En el caso de un iterador de salida, [...] iterator_traits :: value_type [... is ...] se define como nulo". –

+1

Ok, sé que los iteradores de salida tienen 'value_type' establecido en' void'. Con '* i' siendo el' value_type' de 'i', y el programador no puede eliminar la referencia' i', todavía hay un salto para permitir que '* i' sea' void'. No necesita ser 'nulo'. Hay otros usos válidos para 'value_type'. No estoy debatiendo esto, simplemente no entiendo el salto de "no se puede desreferenciar un iterador de salida" a "vamos a definir el' value_type' de un iterador de salida a 'void'". – wilhelmtell