2010-12-02 17 views
9

En ++ C actual, el ostream_iterator clase se diseñó como la siguiente:¿Por qué ostream_iterator necesita declarar explícitamente el tipo de objetos a la salida?

// excerpted from the standard C++ 

template<class T, ...> 
class ostream_iterator 
{ 
public: 
    ostream_iterator(ostream_type&); 
    ... 

    ostream_iterator<T,...>& operator =(const T&); 
    ... 
}; 

Para mí, este diseño no es óptima. Porque el usuario debe especificar el tipo T al declarar un ostream_iterator así: ostream_iterator<int> oi(cout); De hecho, cout puede tomar cualquier tipo de objeto como argumento, en lugar de un solo tipo. Esta es una restricción obvia.

// Below is my own version 

// doesn't need any template parameter here 
class ostream_iterator 
{ 
public: 
    ostream_iterator(ostream_type&); 
    ... 

    // define a template member function which can take any type of argument and output it 
    template<class T> 
    ostream_iterator<T,...>& operator =(const T&); 
    ... 
}; 

Ahora, podemos usar la siguiente manera:

ostream_iterator oi(cout); 

Creo que es más genérico y más elegante que

ostream_iterator<int> oi(cout); 

Estoy en lo cierto?

+0

¿Cuál es la 'value_type' de su ostream_iterator? – MSalters

+0

@MSalters: no es obligatorio que la clase ostream_iterator deba escribir typedef un valor value_type. Así que creo que la deducción del tipo se puede retrasar hasta que se llame al operador =(). – xmllmx

+0

Pehaps No entiendo el problema que intentas resolver. ¿Qué algoritmos vas a usar con tu 'ostream_iterator' que van a generar objetos de diferentes tipos? Si transmite objetos de diferentes tipos, ¿cómo podría saber los tipos correctos para volver a leerlos? –

Respuesta

2

Parece que podría estar en lo cierto.

Veamos si podemos construir un ostream_iterator que no necesita un argumento de plantilla.

El repetidor funciona copiando los valores en ella, por lo *iter = x; ++iter; Los tramposos iterador haciendo operador * volver en sí y también ++iter volviendo en sí, sin cambiar ningún estado. La "magia" está en el operador = que realiza la salida.

El "cout" debe ser un miembro de clase de tipo ostream *. . Tiene que ser un puntero como iteradores deben ser asignable, por lo tanto se le asigna el miembro (lo llaman OS) a la dirección del flujo pasado en

lo tanto, sería sobrecargar el operador = esta manera:

template< typename T > 
our_ostream_iterator& operator=(const T& t) 
{ 
    (*os) << t; 
    if(delim) 
     (*os) << delim; 
    return *this; 
} 

Tenga en cuenta que el operador planificado = no debe oveload operator = (our_ostream_iterator const &) que es más especializado que la plantilla.

a pesar de ello quieren una plantilla en el tipo de elemento por lo que llamaremos que our_basic_ostream_iterator

ostream_iterator seguiría siendo una clase de plantilla en su tipo de elemento.Por lo tanto:

template< typename E, typename TR=char_traits<E> > 
class our_basic_ostream_iterator : public std::iterator< /*traits here*/ > 
{ 
public: 
    typedef E element_type; 
    typedef TR traits_type; 
    typedef basic_ostream< E, TR > stream_type; 
private: 
    stream_type * os; 
    E* delim; 
public: 
    our_basic_ostream_iterator(stream_type s, E* d = NULL) : 
     os(&s), delim(d) 
    { 
    } 

    our_basic_ostream_iterator& operator++() { return *this; } 
    our_basic_ostream_iterator operator++(int) { return *this; } 
    our_basic_ostream_iterator& operator*() { return *this; } 

    template< typename T > 
    our_basic_ostream_iterator& operator=(const T& t); // as above 
}; 

y luego por supuesto

typedef our_basic_ostream_iterator<char> our_ostream_iterator; 
typedef our_basic_ostream_iterator<wchar_t> our_wostream_iterator; 
+0

Sin embargo, esto todavía tiene que usarse como 'our_ostream_iterator ', que es lo que el interlocutor quiere evitar. Para tener la oportunidad de usarlo sin nombrar su tipo, también necesitas algún tipo de función de envoltura que deduzca los argumentos de la plantilla (y luego C++ 0x 'auto' y' decltype' significa que nunca necesitarás usar el tipo, mientras que en C + + 03, a veces, todavía necesitas deletrearlo). –

+0

No estoy seguro de si el OP necesita deducir el tipo de elemento y la amplia entrada/salida del constructor, pero podría usar una función para deducir qué tipo hacer y usar eso, dependiendo de qué flujo pasó. – CashCow

+0

Pasa ' stream_type s' por valor y guarde su dirección en 'os'. – Pixelchemist

0

Sí, tienes razón. Sería más flexible como sugieres. Sin embargo, la forma en que se diseñó se ajusta más a cómo STL usa los iteradores: un tipo de iterador para el tipo de datos (T).

1

Creo que la razón es que también tiene otros miembros. Obviamente, el conjunto completo de funciones miembro debe ser coherente en su comportamiento para un conjunto dado de T y otros argumentos de plantilla.

Hay peligro en operator < ser una instancia para un conjunto de argumentos de plantilla que es diferente de lo que se utiliza para crear una instancia operator * o operator++

Por lo tanto, los métodos individuales no son plantilla de sí mismos y en lugar de toda la clase es una plantilla así garantizar una T uniforme y otros argumentos de plantilla.

1

La respuesta simple es que iterator han asociado tipos y ostream_iterator viola conceptualmente el concepto de un interator requiriendo una value_type incluso cuando no es necesario. (Esta es la respuesta de basicallt @ pts)

Lo que está proponiendo está relacionado con la idea detrás de los nuevos "operadores transparentes", como el nuevo std::plus<void>. Que consisten en tener una instanciación especial cuya función miembro tiene una deducción de tipo demorado.

También es compatible con versiones anteriores porque void no es una instalación útil para empezar. Además, el parámetro void también es el predeterminado. Por ejemplo, template<T = void> struct std::plus{...} es la nueva declaración.


Una posible implementación de un transparente ostream_iterator

Volviendo de std::ostream_iterator, una prueba importante es si queremos hacer que funcione con std::copy como std::ostream_iterator por lo general se utiliza:

std::vector<int> v = {...}; 
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " ")); 

La tecnología para un std::ostream_iterator transparente no está allí todavía, porque esto falla:

std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " ")); 

Para que esto funcione, se puede definir explícitamente la instancia void. (Esto completa respuesta @CashCow 's)

#include<iterator> 
namespace std{ 
    template<> 
    struct ostream_iterator<void> : 
     std::iterator<std::output_iterator_tag, void, void, void, void> 
    { 
     ostream_iterator(std::ostream& os, std::string delim) : 
      os_(os), delim_(delim) 
     {} 
     std::ostream& os_; 
     std::string delim_; 
     template<class T> ostream_iterator& operator=(T const& t){ 
      os_ << t << delim_; 
      return *this; 
     } 
     ostream_iterator& operator*(){return *this;} 
     ostream_iterator& operator++(){return *this;} 
     ostream_iterator& operator++(int){return *this;} 
    }; 

} 

Ahora bien, esto funciona:

std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " ")); 

Por otra parte, si nos convencer al comité estándar para tener un parámetro predeterminado void (como lo hicieron con la std::plus): template<class T = void, ...> struct ostream_iterator{...}, podríamos ir un paso más allá y omitir el parámetro completo:

std::copy(v.begin(), v.end(), std::ostream_iterator<>(std::cout, " ")); 

La raíz del problema y una posible salida

Por último, en mi opinión, el problema también podría ser conceptual, en STL uno espera un iterador que tiene un definido value_type asociados, incluso si no es necesario, como aquí. En cierto sentido, ostream_iterator viola algunos conceptos de lo que es un iterador.

Así que hay dos cosas que son conceptualmente incorrectas en este uso: 1) cuando uno copia uno espera saber el tipo de fuente (contenedor value_type) y tipos de destino 2) ¡uno no está copiando nada en primer lugar! . En mi opinión, hay un error de diseño doble en este uso típico. Debe haber un std::send que funcione con una plantilla shift << operadores directamente, en lugar de hacer = redirigir a << como ostream_iterator hace.

std::send(v.begin(), v.end(), std::cout); // hypothetical syntax 
std::send(v.begin(), v.end(), std::ostream_receiver(std::cout, " ")); // hypothetical syntax 
std::send(v.begin(), v.end(), "some ostream_filter"); // hypothetical syntax 

(El último argumento debe cumplir con algún tipo de Sink concepto).


** El uso de std::accumulate lugar y una posible implementación de std::send **

Desde un punto de vista conceptual, el envío de objetos a una corriente que es más de una operación de "acumular" que un operador de copia, por lo tanto, en principio, std::accumulate debería ser un candidato más adecuado, además de que no necesitamos iteradores de "destino" para él. El problema no es que std::accumulate wa no para hacer copias de cada objeto que se acumula, por lo que esto no funciona:

std::accumulate(e.begin(), e.end(), std::cout, 
     [](auto& sink, auto const& e){return sink << e;} 
    ); // error std::cout is not copiable 

Para que funcione hay que hacer un poco de magia reference_wrapper:

std::accumulate(e.begin(), e.end(), std::ref(std::cout), 
     [](auto& sink, auto const& e){return std::ref(sink.get() << e);} 
    ); 

Finalmente, el código se puede simplificar por tener el equivalente de std::plus para el operador de desplazamiento, en C++ moderno esto debería tener este aspecto IM:

namespace std{ 

    template<class Sink = void, class T = void> 
    struct put_to{ 
     std::string delim_; 
     using sink_type = Sink; 
     using input_type = T; 
     Sink& operator()(Sink& s, T const& t) const{ 
      return s << t << delim_; 
     } 
    }; 

    template<> 
    struct put_to<void, void>{ 
     std::string delim_; 
     template<class Sink, class T> 
     Sink& operator()(Sink& s, T const& t){ 
      return s << t; 
     } 
     template<class Sink, class T> 
     std::reference_wrapper<Sink> operator()(std::reference_wrapper<Sink> s, T const& t){ 
      return s.get() << t << delim_; 
     } 
    }; 

} 

que puede ser utilizado como:

std::accumulate(e.begin(), e.end(), std::ref(std::cout), std::put_to<>{", "}); 

Finalmente podemos definir:

namespace std{ 
    template<class InputIterator, class Sink> 
    Sink& send(InputIterator it1, InputIterator it2, Sink& s, std::string delim = ""){ 
     return std::accumulate(it1, it2, std::ref(s), std::put_to<>{delim}); 
    } 
} 

que puede ser utilizado como

std::send(e.begin(), e.end(), std::cout, ", "); 

Por último, no hay dilema sobre el tipo de cualquier output_iterator aquí.

Cuestiones relacionadas