为什么ostream_iterator需要显式声明要输出的对象类型?
在当前的 C++ 中,类 ostream_iterator 的设计如下:
// excerpted from the standard C++
template<class T, ...>
class ostream_iterator
{
public:
ostream_iterator(ostream_type&);
...
ostream_iterator<T,...>& operator =(const T&);
...
};
对我来说,这种设计不是最优的。因为用户在声明 ostream_iterator 时必须指定类型 T,如下所示: ostream_iterator
事实上,cout 可以将任何类型的对象作为其参数,而不仅仅是一种类型。这是一个明显的限制。
// 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&);
...
};
现在,我们可以按如下方式使用它:
ostream_iterator oi(cout);
我认为它比
ostream_iterator<int> oi(cout);
我更通用、更优雅,对吗?
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(4)
简单的答案是,迭代器具有关联的类型,而 ostream_iterator 在概念上违反了迭代器的概念,因为即使不需要,也需要一个值类型。 (这基本上是@pts的答案)
您提出的建议与新的“透明运算符”背后的想法有关,例如新的
std::plus
。其中包括有一个特殊的实例化,其成员函数具有延迟的类型推导。它也是向后兼容的,因为
void
本来就不是一个有用的实例化。此外,void
参数也是默认参数。例如template struct std::plus{...}
是新的声明。透明
ostream_iterator
的可能实现回到
std::ostream_iterator
,一个重要的测试是我们是否想让它与一起工作通常使用 >std::copy
作为std::ostream_iterator
:透明
std::ostream_iterator
的技术还不存在,因为这会失败:为了实现这一点,可以显式定义
void
实例。 (这完成了 @CashCow 的答案)现在这是可行的:
此外,如果我们说服标准委员会有一个默认的
void
参数(就像他们对std::plus
):模板 struct ostream_iterator{...}
,我们可以更进一步,完全省略参数:问题的根源和可能的出路
最后,在我看来,问题也可能是从概念上讲,在 STL 中,人们期望迭代器具有关联的明确的
value_type
,即使它不像这里那样是必要的。从某种意义上说,ostream_iterator 违反了迭代器的一些概念。因此,这种用法有两件事在概念上是错误的:1)当一个人复制时,他期望知道源的类型(容器
value_type
)和目标类型 2)一个人没有复制首位!。在我看来,这种典型用法存在双重设计错误。应该有一个std::send
可以直接与模板移位<<
运算符一起使用,而不是将=
重定向到<<
就像ostream_iterator
一样。(最后一个参数应该满足某种
Sink
概念)。** 使用
std::accumulate
代替,并可能实现std::send
**从概念的角度来看,将对象发送到流更多的是“累积”操作,而不是复制运算符,因此原则上
std::accumulate 应该是更合适的候选者,此外我们不需要它的“目标”迭代器。
问题是 std::accumulate 想要复制正在累积的每个对象,因此这是行不通的:
为了使其工作,我们需要做一些
reference_wrapper
magic:最后,可以通过使用相当于
std::plus
的移位运算符来简化代码,在现代 C++ 中,这应该如下所示 IM:可以用作:
最后我们可以定义:
可以用作
最后,这里任何
output_iterator
的类型都不存在困境。The simple answer is that
iterator
have associated types andostream_iterator
conceptually violates the concept of an iterator by requiring avalue_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 thevoid
parameter is also the default. For exampletemplate<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 withstd::copy
asstd::ostream_iterator
is usually used:The technology for a transparent
std::ostream_iterator
is not there yet, because this fails:To make this work, one can explicitly define the
void
instance. (This completes @CashCow 's answer)Now this works:
Moreover, if we convince the standard committee to have a default
void
parameter (as they did with withstd::plus
):template<class T = void, ...> struct ostream_iterator{...}
, we could go a step further and omit the parameter altogether: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 senseostream_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 astd::send
that works with a template shift<<
operators directly, instead of making=
redirect to<<
asostream_iterator
does.(The last argument should fulfill some kind of
Sink
concept).** Using
std::accumulate
instead and a possible implementation ofstd::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:To make it work we need to do some
reference_wrapper
magic: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:Which can be used as:
Finally we can define:
Which can be used as
Finally, there is no dilemma about the type of any
output_iterator
here.看来你可能是对的。
让我们看看是否可以构造一个不需要模板参数的 ostream_iterator。
迭代器通过将值复制到其中来工作,因此
*iter = x; ++iter;
迭代器通过让operator*返回自身并且
++iter
也返回自身而不改变任何状态来作弊。 “魔力”在于执行输出的operator=。“cout”必须是 ostream* 类型的类成员。它需要是一个指针,因为迭代器必须是可分配的,因此我们将成员(称为 os)分配给传入的流的地址。
因此,我们将这样重载operator=:
请注意,模板化的operator= 不应重载运算符=(our_ostream_iterator const&) 比模板更专业。
您仍然需要元素类型上的模板,因此我们将调用
our_basic_ostream_iterator
ostream_iterator 仍将保留其元素类型上的模板类。因此:
当然,
所有这些的缺点是上面的内容不符合迭代器的所有属性,因此它可以传递给任何需要前向迭代器的算法/类。为什么?因为这样的算法应该能够调用iterator_traits来提取元素类型,而上面的类不包含元素类型。
这会导致使用迭代器的算法出现编译时错误,并且可能很难找出原因。
It appears you could be right.
Let's see if we can construct an ostream_iterator that does not need a template argument.
The iterator works by copying values into it, so
*iter = x; ++iter;
The iterator cheats by making operator* return itself and
++iter
also returning itself without changing any state. The "magic" is in the operator= which performs the output.The "cout" must be a class member of type ostream*. It needs to be a pointer as iterators must be assignable, thus we assign the member (call it os) to the address of the stream passed in.
So we would overload operator= this way:
Note that the templatised operator= should not oveload operator=(our_ostream_iterator const&) which is more specialised than the template.
You would still want a template on the element type so we will call that
our_basic_ostream_iterator
ostream_iterator would still remain a template class on its element type. Thus:
and then of course
The drawback of all of this though is that the above does not conform to all the properties of iterators such that it could be passed to any algorithm / class that requires a forward iterator. Why? Because such an algorithm should be able to invoke iterator_traits to extract the element type and the class above does not contain an element type.
It would lead to compile-time errors in the algorithm that is using your iterator and would potentially be hard to track down the reason why.
我认为原因是它还有其他成员。显然,对于给定的一组 T 和其他模板参数,整个成员函数集的行为需要保持一致。
为一组模板参数实例化
operator <
存在危险,这与实例化operator *
或operator++
所使用的参数不同,因此,各个方法本身不是模板,而是整个类是模板,因此请确保统一的 T 和其他模板参数。
I think the reason is that it has other members also. Obviously the entire set of member functions need to be consistent in their behavior for a given set of T and other template arguments.
There's danger in
operator <
being instantiated for a set of template arguments which is different from what is used to instantiateoperator *
oroperator++
Hence, the individual methods are not template themselves and rather the entire class is a template so ensure uniform T and other template arguments.
是的你是对的。按照你的建议,它会更灵活。然而,它的设计方式更适合 STL 使用迭代器的方式:数据类型 (T) 的一种迭代器类型。
Yes, you are right. It would be more flexible as you suggest. However, the way it's designed fits more closely to how STL uses iterators: one iterator type for data type (T).