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í.
¿Cuál es la 'value_type' de su ostream_iterator? – MSalters
@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
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? –