在C#中,您可以非常简单地定义自定义枚举,例如:
public IEnumerable<Foo> GetNestedFoos()
{
foreach (var child in _SomeCollection)
{
foreach (var foo in child.FooCollection)
{
yield return foo;
}
foreach (var bar in child.BarCollection)
{
foreach (var foo in bar.MoreFoos)
{
yield return foo;
}
}
}
foreach (var baz in _SomeOtherCollection)
{
foreach (var foo in baz.GetNestedFoos())
{
yield return foo;
}
}
}
(使用LINQ和更好的封装可以简化这一点,但这不是问题的关键。(
在C++11中,您可以进行类似的枚举,但AFAIK需要一个访问者模式:
template<typename Action>
void VisitAllFoos(const Action& action)
{
for (auto& child : m_SomeCollection)
{
for (auto& foo : child.FooCollection)
{
action(foo);
}
for (auto& bar : child.BarCollection)
{
for (auto& foo : bar.MoreFoos)
{
action(foo);
}
}
}
for (auto& baz : m_SomeOtherCollection)
{
baz.VisitAllFoos(action);
}
}
有没有一种方法可以做得更像第一种方法,即函数返回一个可以在外部迭代的范围,而不是在内部调用访问者?
(我的意思不是构建一个std::vector<Foo>
并返回它——它应该是一个就地枚举。(
我知道Boost.Range库,我怀疑它会参与解决方案,但我不是特别熟悉。
我也知道可以定义自定义迭代器来做这类事情(我也怀疑答案中可能涉及到这一点(,但我正在寻找一些易于编写的东西,理想情况下不会比这里显示的例子更复杂,并且可以组合(比如_SomeOtherCollection
(。
我更喜欢不需要调用方使用lambda或其他函数的东西(因为这只会让它再次成为访问者(,尽管如果需要的话,我不介意在内部使用lambda(但仍然希望在那里避免使用它们(。
如果我正确理解您的问题,那么您希望对集合的所有元素执行一些操作。
C++有一组广泛的迭代器操作,在迭代器头中定义。大多数集合结构,包括您引用的std::vector
,都有.begin
和.end
方法,它们不带参数,并在结构的开头和结尾返回迭代器。这些迭代器有一些操作可以手动执行,但它们的主要用途是算法头的形式,它定义了几个非常有用的迭代函数。
在您的特定情况下,我相信您想要for_each
函数,它接受一个范围(作为从开始到结束的迭代器(和一个要应用的函数。因此,如果您有一个名为action
的函数(或函数对象(,并且您想将其应用于一个称为data
的向量,则以下代码将是正确的(假设所有必要的标头都包含在内(:
std::for_each(data.begin(), data.end(), action);
注意,for_each
只是算法报头提供的许多函数中的一个。它还提供了搜索集合、复制数据集、排序列表、查找最小值/最大值等功能,所有这些功能都可以通用于任何具有迭代器的结构。如果这些还不够,您可以通过阅读迭代器上支持的操作来编写自己的操作。只需定义一个使用不同类型迭代器的模板函数,并记录您想要的迭代器类型。
template <typename BidirectionalIterator>
void function(BidirectionalIterator begin, BidirectionalIterator end) {
// Do something
}
最后要注意的一点是,到目前为止提到的所有操作都可以在数组上正确操作,前提是您知道数组的大小。不编写.begin
和.end
,而是编写+ 0
和+ n
,其中n
是数组的大小。为了将数组的类型衰减为指针以使其成为有效的迭代器,通常需要进行琐碎的零加法,但数组指针实际上是随机访问迭代器——就像任何其他容器迭代器一样。
您可以编写自己的适配器函数,并用相同类型的不同范围的元素调用它。
这是一个未经测试的解决方案,可能需要一些调整才能编译,但它会给你一个想法。它使用可变模板从一个集合移动到下一个集合。
template<typename Iterator, Args...>
visitAllFoos(std::pair<Iterator, Iterator> collection, Args&&... args)
{
std::for_each(collection.first, collection.second, {}(){ // apply action });
return visitAllFoos(std::forward<Args>(args)...);
}
//you can call it with a sequence of begin/end iterators
visitAllFoos(std::make_pair(c1.begin(), c1,end()), std::make_pair(c2.begin(), c2,end()))
我相信,您想要做的事情可以用Boost.Range来完成,尤其是用join
和any_Range(如果您想隐藏容器的类型并从接口中删除joined_range
,则需要后者(。
然而,所得到的解决方案在复杂性和性能方面都不太实用,这主要是因为嵌套的joined_range
和any_range
引起的类型擦除开销。就我个人而言,我只会构建std::vector<Foo*>
或使用访问。
您可以在boost::asio::coroutine
的帮助下完成此操作;请参阅上的示例https://pubby8.wordpress.com/2014/03/16/multi-step-iterators-using-coroutines/和http://www.boost.org/doc/libs/1_55_0/doc/html/boost_asio/overview/core/coroutine.html.