【问题标题】:Make std::for_each more useful - is this a good idea for the functor to know the current index?使 std::for_each 更有用 - 这是函子知道当前索引的好主意吗?
【发布时间】:2013-07-21 21:19:09
【问题描述】:

通常我不能使用 std::for_each 因为我对特定元素的逻辑取决于它的当前索引。为此,我发明了一个函子类,它包装了主函子并将当前索引传递给它。理想情况下,我想将它与 lambda 表达式一起使用。我创建的课程安全有效吗?有没有更好的解决方案?我确实希望包装器的运算符 () 返回 lambda 表达式的类型,但我无法弄清楚。另外,我应该为索引使用什么类型?我应该通过值还是引用将主函子存储在包装器中?

谢谢!

template<class FUNC>
class IndexFunctor
{
public:
    typedef FUNC FUNC_T;

    explicit IndexFunctor(const FUNC_T& func) : func(func), index(0) {}

    // how can this return the return type of func?
    template<class T>
    void operator ()(T& param)
    {
        func(index++, param);
    }

    const FUNC_T& GetFunctor() const
    {
        return func;
    }

    int GetIndex() const
    {
        return index;
    }

    void SetIndex(int index)
    {
        this->index = index;
    }

private:
    FUNC_T func;
    int index;
};

template<class FUNC>
IndexFunctor<FUNC> with_index(const FUNC& func)
{
    return IndexFunctor<FUNC>(func);
}

void somefunc()
{
    std::vector<int> v(10);
    std::for_each(v.begin(), v.end(), with_index([](int index, int x){ std::cout << "[" << index << "]=" << x << std::endl; }));
}

【问题讨论】:

  • 其中一些问题可能更适合Code Review
  • 为什么不直接使用for 循环呢?我不觉得std::for_each 有帮助,而且我通常对使用算法无处不在而不是循环感到厌烦。
  • 我无法谈论 Neil 的情况,但在您有一对迭代器但无法获得范围大小但仍需要索引的情况下,它可能很有用。当然,这是一种罕见的情况,但你永远不会知道。

标签: c++ c++11 functor


【解决方案1】:

这应该是安全的,尽管自己编写索引 for-each 相当简单。

template <typename TInputIterator, typename TFunc>
TFunc counted_for_each(TInputIterator first, TInputIterator last, TFunc func)
{
    for (size_t i = 0; first != last; ++first)
    {
        func(i++, *first);
    }

    return func;
}

更少的代码,完成同样的事情。

【讨论】:

  • 哇,最简单的解决方案有时会避开我们!为什么我不这样做。
  • 我建议考虑 snipes83 提出的答案,因为这样您就可以使用标准库 for_each 并且仍然可以得到您想要的。例如,我这个循环的初始版本在 first 上使用了后增量,而它确实应该使用前增量。
  • 我不喜欢这个答案,因为我必须在循环外创建一个索引变量。
  • 很公平。这个版本将在某些方面产生更清晰的最终结果,而另一个答案让标准库编写者担心得到正确的细节:)
  • 这就是我的包装器进来的地方;)
【解决方案2】:

您从一开始就遇到的问题是您想使用std::for_each 跟踪索引/计数器?然后一种解决方案是简单地使用一个计数器变量并让例如 lambda 表达式捕获它。

std::vector<int> v{ 1, 2, 3 };

std::vector<int>::size_type i = 0;

std::for_each(begin(v), end(v), [i] (int x) mutable {
    std::cout << "[" << i++ << "]=" << x << std::endl;
});

或者你可以简单地这样做:

std::vector<int>::size_type i = 0;
for (auto itr = begin(v); itr != end(v); ++itr) {
    std::cout << "[" << i++ << "]=" << *itr << std::endl;
}

在 C++11 中使用基于范围的 for 循环可以简化为:

std::vector<int>::size_type i = 0;
for (auto itr : v) {
    std::cout << "[" << i++ << "]=" << itr << std::endl;
}

注意:在上面最后两个示例中,您必须记住每次运行 for 循环时都将索引计数器重置为 0。

更新:如果您想遍历一个跟踪索引的子范围,同时又不想在循环之外有一个索引变量,您可以使用std::distance 计算索引,如下所示:

for (auto itr = begin(v); itr != end(v); ++itr) {
    std::cout << "[" << std::distance(begin(v),itr) << "]=" << *itr << std::endl;
}

同样使用 C++14 通用 Lambda 捕获表达式,您可以省略外部范围内的计数器变量,只需使用初始化表达式创建 i 作为 lambda 成员:

std::for_each(begin(v), end(v), [i = 0] (int x) mutable {
    std::cout << "[" << i++ << "]=" << x << std::endl;
});

【讨论】:

  • 实际上,我比我更喜欢这个解决方案。使用 lambdas,真的不需要自定义算法或特殊函子。
  • 您可以通过按值捕获来避免重置索引,然后将 lambda 标记为mutable。然后它会在创建时复制索引,然后可以更新本地副本中的值。
  • @DaveS 这是一个很好的观点。我已经更新了答案中的 lambda 表达式。
【解决方案3】:

在我看来,std::for_each 在基于范围的for( : ) 循环中已经过时了,但迭代子范围可能是个例外。

即便如此,我也只是有一个 range_view template 可以与基于范围的 for( : ) 循环一起使用。 range_view 是一个简单的结构,带有返回迭代器的 begin()end() 方法:这样的对象可以传递给基于范围的 for 循环:

template<typename Iterator>
struct range_view {
  Iterator b, e;
  Iterator begin() const { return b; }
  Iterator end() const { return e; }
};
template<typename Iterator>
range_view<Iterator> make_range_view( Iterator b, Iterator e ) {
  return {b,e};
}
template<typename Container>
auto make_range_view( Container&& c )
-> decltype( make_range_view( std::begin(c), std::end(c) ) )
{   return ( make_range_view( std::begin(c), std::end(c) ) ); }

如果我需要索引,我会使用indexes 范围,或者如果非常热衷于索引范围和原始容器(生成元组范围)上的范围-zip 适配器。 indexes 是一个迭代器连续整数(boost 有这种类型),zip 是取两个范围或容器并返回一个范围超过tuplepairs 的数据的操作.

现在我们可以修复您的问题。升级() 以使用完美转发和auto-&gt;decltype 返回值。但我不会打扰:std 算法有它们的位置,但for_each 很少值得。

【讨论】:

  • 我不明白你的回答。什么是 range_view 和 zip 适配器?
【解决方案4】:

我做了这个小算法,基于 for_each:

namespace estd {

template<class InputIt, class BinaryFunction>
BinaryFunction for_each_with_index(
    InputIt first, InputIt last, BinaryFunction f)
{
  for (auto index = 0; first != last; ++first, ++index) {
    f(*first, index);
  }
  return f;
}

}

然后我可以写一个像这样的简单示例:

estd::for_each_with_index(
    begin(strings_),
    end(strings_),
    [&sx, &sy](const auto& s, auto index) {
       // Do something with my string and integer index
});

estd::for_each_with_index 将适用于任何前向迭代器(任何提供 *、++、!= 的东西)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-08-10
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2018-01-03
    • 2010-12-04
    • 2014-08-14
    相关资源
    最近更新 更多