C++创建输出存储而不是应用于现有存储的算法?
C++ std 算法 定义了许多采用输入和输出序列的算法,并且从输入序列的元素创建输出序列的元素。 (最好的例子是 std::transform
。)
std 算法显然采用迭代器,因此毫无疑问,OutputIterator 的容器必须在调用算法之前存在。
也就是说:
std::vector<int> v1; // e.g. v1 := {1, 2, 3, 4, 5};
std::vector<int> squared;
squared.reserve(v1.size()); // not strictly necessary
std::transform(v1.begin(), v1.end(), std::back_inserter(squared),
[](int x) { return x*x; } ); // λ for convenience, needn't be C++11
就 std 库而言,这个没问题。当我发现迭代器太麻烦时,我经常查看 Boost.Range 来简化事情。
然而,在这种情况下,似乎 Boost.Range 中的变异算法 也使用 OutputIterators
。
所以我目前想知道是否有任何方便的库,它允许我编写:
std::vector<int> const squared = convenient::transform(v1, [](int x) { return x*x; });
- 如果没有,是否有一个原因没有 >?
编辑:示例实现(不确定这是否适用于所有情况,以及这是否是最理想的):(
template<typename C, typename F>
C transform(C const& input, F fun) {
C result;
std::transform(input.begin(), input.end(), std::back_inserter(result), fun);
return result;
}
注意:我认为 convenient::transform
将有与手写的性能特征相同,因为由于 (N)RVO,返回的向量不会被复制。无论如何,我认为性能对于这个问题来说是次要的。)
编辑/注意:给出的答案(评论,真的)到目前为止,大卫给出 一个非常好的基本通用示例。
和 卢克提到 std::back_inserter
wrt 可能存在的问题。通用性。
两者都只是为了说明为什么我在犹豫是否要自己完成这个,以及为什么“适当的”(经过适当测试的)库比我自己编码更可取。
我的问题在上面用粗体表达,即是否存在,或者是否有理由不存在,但仍然基本上没有得到解答。
The C++ std algorithms define a number of algorithms that take an input and an output sequence, and create the elements of the output sequence from the elements of the input sequence. (Best example being std::transform
.)
The std algorithms obviously take iterators
, so there's no question that the container for the OutputIterator
has to exist prior to the algorithm being invoked.
That is:
std::vector<int> v1; // e.g. v1 := {1, 2, 3, 4, 5};
std::vector<int> squared;
squared.reserve(v1.size()); // not strictly necessary
std::transform(v1.begin(), v1.end(), std::back_inserter(squared),
[](int x) { return x*x; } ); // λ for convenience, needn't be C++11
And this is fine as far as the std library goes. When I find iterators too cumbersome, I often look to Boost.Range to simplify things.
In this case however, it seems that the mutating algorithms in Boost.Range also use OutputIterators
.
So I'm currently wondering whether there's any convenient library out there, that allows me to write:
std::vector<int> const squared = convenient::transform(v1, [](int x) { return x*x; });
-- and if there is none, whether there is a reason that there is none?
Edit: example implementation (not sure if this would work in all cases, and whether this is the most ideal one):
template<typename C, typename F>
C transform(C const& input, F fun) {
C result;
std::transform(input.begin(), input.end(), std::back_inserter(result), fun);
return result;
}
(Note: I think convenient::transform
will have the same performance characteristics than the handwritten one, as the returned vector won't be copied due to (N)RVO. Anyway, I think performance is secondary for this question.)
Edit/Note: Of the answers(comments, really) given so far, David gives a very nice basic generic example.
And Luc mentions a possible problem with std::back_inserter
wrt. genericity.
Both just go to show why I'm hesitating to whip this up myself and why a "proper" (properly tested) library would be preferable to coding this myself.
My question phrased in bold above, namely is there one, or is there a reason there is none remains largely unanswered.
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(5)
这并不是对问题本身的回答,而是对其他答案的补充——但它不适合评论。
虽然这适用于少数容器,但它仍然不是非常通用,因为它要求容器与
std::back_inserter(c)
(BackInsertable?)“兼容”。如果c.push_back()
不可用(左),您可能可以使用 SFINAE 来代替使用std::inserter
和c.begin()
作为读者的练习)。所有这些还假设容器是可默认构造的——考虑使用作用域分配器的容器。据推测,通用性的丧失是一个特征,因为我们只是试图涵盖“最简单”的用途。
事实上,虽然我不会使用这样的库:我不介意在算法旁边创建容器来分离关注点。 (我想这可以被认为是我对这个问题的回答。)
This is not meant as an answer to the question itself, it's a complement to the other answers -- but it wouldn't fit in the comments.
While this would work with a handful of containers, it's still not terribly generic since it requires that the container be 'compatible' with
std::back_inserter(c)
(BackInsertable?). Possibly you could use SFINAE to instead usestd::inserter
withc.begin()
ifc.push_back()
is not available (left as an exercise to the reader).All of this also assume that the container is DefaultConstructible -- consider containers that make use of scoped allocators. Presumably that loss of genericity is a feature, as we're only trying to cover the 'simplest' uses.
And this is in fact while I would not use such a library: I don't mind creating the container just outside next to the algorithm to separate the concerns. (I suppose this can be considered my answer to the question.)
恕我直言,这种算法的要点是通用,即主要与容器无关。您建议的是
transform
函数非常具体,并返回一个std::vector
,好吧 - 如果您想要list
或deque
或其他一些序列类型容器 - 它的限制很大。如果你觉得它很烦人,为什么不包起来呢?创建您自己的小实用程序标头来执行此操作 - 毕竟,它非常微不足道......
IMHO, the point of such an algorithm is to be generic, i.e. mostly container agnostic. What you are proposing is that the
transform
function be very specific, and return astd::vector
, well - what if you wantedlist
ordeque
or some other sequence type container - it's pretty limiting.Why not wrap if you find it so annoying? Create your own little utilities header which does this - after all, it's pretty trivial...
Boost.Range.Adaptors< /a> 可以看作是容器返回算法。为什么不使用它们呢?
唯一需要做的就是定义一个新的范围适配器
create
,它可以通过管道传输到适应的范围并生成所需的结果容器:是的,就是这样。不需要任何其他东西。只需将其放在适配器列表的末尾即可。
这里可能是 Ideone 上的一个实例。可惜,事实并非如此,因为 Ideone在 C++0x 模式下不提供 Boost.. 嗯。无论如何,这里是
main
和输出:输出:
The Boost.Range.Adaptors can be kind of seen as container-returning algorithms. Why not use them?
The only thing that needs to be done is to define a new range adaptor
create<T>
that can be piped into the adapted ranges and produces the desired result container:Yep, that's it. No need for anything else. Just pipe that at the end of your adaptor list.
Here could be a live example on Ideone. Alas, it isn't, because Ideone doesn't provide Boost in C++0x mode.. meh. In any case, here's
main
and the output:Output:
没有一种正确的启用方式
不会带来潜在的性能成本。您要么需要显式地
注意容器类型的显式提及:迭代器不会告诉任何有关它们所属容器的信息。如果您提醒标准允许容器的迭代器是普通指针,这一点就会变得显而易见。
让算法采用容器而不是迭代器也不是解决方案。这样,算法就无法知道如何正确获取第一个和最后一个元素。例如,
long int
数组没有begin()
、end()
和length()
方法code>,并非所有容器都有随机访问迭代器,未定义operator[]
。因此,没有真正通用的方法来获取容器。允许与容器无关的容器返回算法的另一种可能性是某种通用工厂(请参见 http: //ideone.com/7d4E2):
除了模板化的强制转换运算符之外,没有太多魔力。然后,您从算法中返回该内容:
最后像这样使用它来编写魔术代码:
如您所见,在
operator T
中有一个范围副本。如果目标容器和源容器属于同一类型,则可以通过模板专门化来进行移动。
编辑:正如大卫指出的,您当然可以在转换运算符内完成真正的工作,这可能不会产生额外的成本(通过更多的工作,可以更方便地完成;这只是为了演示):
一个要求是目标有一个
push_back
方法。如果目标容器迭代器不是随机访问迭代器,则使用 std::distance 来保留大小可能会导致性能次优。There is no one and correct way of enabling
without a potential performance cost. You either need an explicit
Note the explicit mentioning of the container type: Iterators don't tell anything about the container they belong to. This becomes obvious if you remind that a container's iterator is allowed by the standard to be an ordinary pointer.
Letting the algorithm take a container instead of iterators is not a solution, either. That way, the algorithm can't know how to correctly get the first and last element. For example, a
long int
-array does not have methods forbegin()
,end()
andlength()
, not all containers have random access iterators, notoperator[]
defined. So there is no truly generic way to take containers.Another possibility that allows for container-agnostic, container-returning algorithms would be some kind of generic factory (see live at http://ideone.com/7d4E2):
Not so much magic there apart from the templated cast operator. You then return that thing from your algorithm:
And finally use it like this to write magic code:
As you see, in
operator T
there's a range copy.A move might be possible by means of template specialization in case the target and source containers are of the same type.
Edit: As David points out, you can of course do the real work inside the conversion operator, which will come at probably no extra cost (with some more work it can be done more convenient; this is just for demonstration):
The one requirement is that the target has a
push_back
method. Usingstd::distance
to reserve a size may lead to sub-optimal performance if the target-container-iterator is not a random-access one.再次,没有答案,而是从评论到另一个 答案
关于问题代码中返回类型的通用性
代码为它不允许返回类型的转换,但是可以通过提供两个模板轻松解决:
Again, a no-answer, but rather a follow up from the comments to another answer
On the genericity of the returned type in the questions code
The code as it stands does not allow the conversion of the return type, but that can be easily solvable by providing two templates: