In current C++, the class ostream_iterator was designed like the following:
// excerpted from the standard C++
template<class T, ...>
class ostream_iterator
{
public:
ostream_iterator(ostream_type&);
...
ostream_iterator<T,...>& operator =(const T&);
...
};
To me, this design is suboptimal. Because the user must specify the type T when declaring an ostream_iterator like this: ostream_iterator<int> oi(cout); In fact, cout can take any type of object as its argument, rather than only one type. This is an obvious restriction.
// 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&);
...
};
Now, we can use it as follows:
ostream_iterator oi(cout);
I think it is more generic and more elegant than
ostream_iterator<int> oi(cout);
Am I right?
The simple answer is that iterator have associated types and ostream_iterator conceptually violates the concept of an iterator by requiring a value_type even when it is not necessary. (This is basically @pts's answer)
What you are proposing is related to the idea behind the new "transparent operators", such as the new std::plus<void>. Which consist in having a special instantiation whose member function has a delayed type deduction.
It is also backward compatible because void is not a useful instantiation to begin with. Moreover the void parameter is also the default. For example template<T = void> struct std::plus{...} is the new declaration.
A possible implementation of a transparent ostream_iterator
Going back of std::ostream_iterator, an important test is whether we want to make it work with std::copy as std::ostream_iterator is usually used:
std::vector<int> v = {...};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
The technology for a transparent std::ostream_iterator is not there yet, because this fails:
std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " "));
To make this work, one can explicitly define the void instance. (This completes @CashCow 's answer)
#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;}
};
}
Now this works:
std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " "));
Moreover, if we convince the standard committee to have a default void parameter (as they did with with std::plus):
template<class T = void, ...> struct ostream_iterator{...}, we could go a step further and omit the parameter altogether:
std::copy(v.begin(), v.end(), std::ostream_iterator<>(std::cout, " "));
The root of the problem and a possible way out
Finally, in my opinion the problem might also be conceptual, in STL one expects an iterator to have a definite value_type associated even if it is not necessary like here. In some sense ostream_iterator violates some concepts of what is an iterator.
So there are two things that are conceptually wrong in this usage: 1) when one copies one expects to know the type of the source (container value_type) and target types 2) one is not copying anything in the first place!. In my opinion there is a double design mistake in this typical usage. There should be a std::send that works with a template shift << operators directly, instead of making = redirect to << as ostream_iterator does.
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
(The last argument should fulfill some kind of Sink concept).
** Using std::accumulate instead and a possible implementation of
std::send **
From a conceptual point of view, sending objects to a stream is more of an "accumulate" operation than a copy operator, so in principle std::accumulate should be a more suitable candidate, besides we don't need "target" iterators for it.
The problem is that std::accumulate wants to make copies of every object that is being accumulated, so this doesn't work:
std::accumulate(e.begin(), e.end(), std::cout,
[](auto& sink, auto const& e){return sink << e;}
); // error std::cout is not copiable
To make it work we need to do some reference_wrapper magic:
std::accumulate(e.begin(), e.end(), std::ref(std::cout),
[](auto& sink, auto const& e){return std::ref(sink.get() << e);}
);
Finally, the code can be simplified by having the equivalent of std::plus for the shift operator, in modern C++ this should look like this 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_;
}
};
}
Which can be used as:
std::accumulate(e.begin(), e.end(), std::ref(std::cout), std::put_to<>{", "});
Finally we can define:
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});
}
}
Which can be used as
std::send(e.begin(), e.end(), std::cout, ", ");
Finally, there is no dilemma about the type of any output_iterator here.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With