简单的答案是iterator 具有关联类型,而ostream_iterator 在概念上违反了迭代器的概念,即使在没有必要的情况下也需要value_type。 (这基本上是@pts 的回答)
您提出的内容与新的“透明运算符”背后的想法有关,例如新的std::plus<void>。其中包括具有一个特殊的实例化,其成员函数具有延迟类型推导。
它也是向后兼容的,因为void 一开始就不是一个有用的实例化。此外,void 参数也是默认值。例如template<T = void> struct std::plus{...} 是新的声明。
透明ostream_iterator的可能实现
回到std::ostream_iterator,一个重要的测试是我们是否想让它与std::copy一起工作,因为通常使用std::ostream_iterator:
std::vector<int> v = {...};
std::copy(v.begin(), v.end(), std::ostream_iterator<int>(std::cout, " "));
透明std::ostream_iterator 的技术尚不存在,因为它失败了:
std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " "));
要完成这项工作,可以显式定义void 实例。 (这完成了@CashCow 的回答)
#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;}
};
}
现在可以了:
std::copy(v.begin(), v.end(), std::ostream_iterator<void>(std::cout, " "));
此外,如果我们说服标准委员会有一个默认的void 参数(就像他们对std::plus 所做的那样):
template<class T = void, ...> struct ostream_iterator{...},我们可以更进一步,完全省略参数:
std::copy(v.begin(), v.end(), std::ostream_iterator<>(std::cout, " "));
问题的根源和可能的出路
最后,在我看来,这个问题也可能是概念性的,在 STL 中,人们期望迭代器有一个明确的 value_type 关联,即使这里没有必要也是如此。在某种意义上ostream_iterator 违反了迭代器的一些概念。
所以在这种用法中有两件事在概念上是错误的:1)当一个人希望知道源(容器value_type)和目标类型的类型时,一个复制者一开始就没有复制任何东西!.在我看来,这种典型用法存在双重设计错误。应该有一个std::send 可以直接与模板移位<< 运算符一起使用,而不是像ostream_iterator 那样使= 重定向到<<。
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
(最后一个参数应该满足某种Sink 概念)。
** 改用std::accumulate 并可能实现
std::send**
从概念的角度来看,将对象发送到流中更多的是“累积”操作而不是复制操作符,因此原则上std::accumulate 应该是更合适的候选者,此外我们不需要“目标”它的迭代器。
问题是std::accumulate 想要复制每个正在积累的对象,所以这不起作用:
std::accumulate(e.begin(), e.end(), std::cout,
[](auto& sink, auto const& e){return sink << e;}
); // error std::cout is not copiable
为了让它发挥作用,我们需要做一些reference_wrapper 魔术:
std::accumulate(e.begin(), e.end(), std::ref(std::cout),
[](auto& sink, auto const& e){return std::ref(sink.get() << e);}
);
最后,代码可以通过等效于 std::plus 的移位运算符来简化,在现代 C++ 中,这应该看起来像这样 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_;
}
};
}
可以用作:
std::accumulate(e.begin(), e.end(), std::ref(std::cout), std::put_to<>{", "});
最后我们可以定义:
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});
}
}
可以用作
std::send(e.begin(), e.end(), std::cout, ", ");
最后,这里没有任何output_iterator的类型问题。