如何使用for_each获取向量中的值的索引?

时间:2010-09-20 13:41:21

标签: c++ c++11 lambda

我有以下代码(编译器:MSVC ++ 10):

std::vector<float> data;
data.push_back(1.0f);
data.push_back(1.0f);
data.push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [](int value) {
     // Can I get here index of the value too?
});

我在上面的代码片段中想要的是获取lambda表达式中数据向量中值的索引。似乎for_each只接受单个参数函数。使用for_each和lambda有什么替代方法吗?

10 个答案:

答案 0 :(得分:25)

我认为你不能捕获索引,但你可以使用外部变量来做索引,将其捕获到lambda中:

int j = 0;
std::for_each(data.begin(), data.end(), [&j](float const& value) {
            j++;
});
std::cout << j << std::endl;

按预期打印3,j保存索引的值。

如果你想要实际的迭代器,你可以这样做:

std::vector<float>::const_iterator it = data.begin();
std::for_each(data.begin(), data.end(), [&it](float const& value) {
            // here "it" has the iterator
            ++it; 
});

答案 1 :(得分:16)

这样的事情:

template <typename IteratorT, typename FunctionT>
FunctionT enumerate(IteratorT first, 
                    IteratorT last, 
                    typename std::iterator_traits<IteratorT>::difference_type initial,
                    FunctionT func)
{
    for (;first != last; ++first, ++initial)
        func(initial, *first);
    return func;
}

用作:

enumerate(data.begin(), data.end(), 0, [](unsigned index, float val)
{
    std::cout << index << " " << val << std::endl;
});

答案 2 :(得分:13)

在C ++ 14中感谢generalized lambda captures,您可以这样做:

std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [idx = 0] (int i) mutable {
    // your code...
    ++idx; // 0, 1, 2... 9
});

答案 3 :(得分:9)

或者,你可以使用&amp; value - &amp; data [0],虽然它可能有点贵。

std::for_each(data.begin(), data.end(), [&data](float const& value) {
    int idx = &value - &data[0];
});

答案 4 :(得分:9)

我认为最简单的方法是使用std::accumulate

std::accumulate(data.begin(), data.end(), 0, [](int index, float const& value)->int{
    ...
    return index + 1;
});

此解决方案适用于任何容器,并且不需要变量或自定义类。

答案 5 :(得分:2)

Roger Pate在评论中建议我创建一个执行枚举的迭代器包装器。实施它有点挨打。

这个迭代器包装器接受一个前向迭代器,它的值类型为T(称为“内部迭代器”),并将其转换为一个前向迭代器,其值类型为pair<int, T&>,其中{{1} }是内迭代器的距离类型。

除了两件事之外,这很简单:

  • int构造函数通过const引用获取其参数,因此我们无法初始化类型为std::pair的数据成员;我们必须为迭代器创建自己的对类型。
  • 为了支持迭代器的正确语义,我们需要一个左值(T&需要返回一个引用而operator*需要返回一个指针),所以这对需要是一个数据迭代器的成员。由于它包含一个引用,我们需要一种“重置”它的方法,我们需要它被懒惰地初始化,以便我们可以正确处理结束迭代器。如果operator->无法分配,boost::optional<T>似乎不喜欢它,因此我们将编写自己的简单T

lazy<T>包装器:

lazy<T>

#include <new> #include <type_traits> // A trivial lazily-initialized object wrapper; does not support references template<typename T> class lazy { public: lazy() : initialized_(false) { } lazy(const T& x) : initialized_(false) { construct(x); } lazy(const lazy& other) : initialized_(false) { if (other.initialized_) construct(other.get()); } lazy& operator=(const lazy& other) { // To the best of my knowledge, there is no clean way around the self // assignment check here since T may not be assignable if (this != &other) construct(other.get()); return *this; } ~lazy() { destroy(); } void reset() { destroy(); } void reset(const T& x) { construct(x); } T& get() { return reinterpret_cast< T&>(object_); } const T& get() const { return reinterpret_cast<const T&>(object_); } private: // Ensure lazy<T> is not instantiated with T as a reference type typedef typename std::enable_if< !std::is_reference<T>::value >::type ensure_t_is_not_a_reference; void construct(const T& x) { destroy(); new (&object_) T(x); initialized_ = true; } void destroy() { if (initialized_) reinterpret_cast<T&>(object_).~T(); initialized_ = false; } typedef typename std::aligned_storage< sizeof T, std::alignment_of<T>::value >::type storage_type; storage_type object_; bool initialized_; };

enumerating_iterator

测试存根:

#include <iterator>
#include <type_traits>

// An enumerating iterator that transforms an iterator with a value type of T
// into an iterator with a value type of pair<index, T&>.
template <typename IteratorT>
class enumerating_iterator
{
public:

    typedef IteratorT                              inner_iterator;
    typedef std::iterator_traits<IteratorT>        inner_traits;
    typedef typename inner_traits::difference_type inner_difference_type;
    typedef typename inner_traits::reference       inner_reference;

    // A stripped-down version of std::pair to serve as a value type since
    // std::pair does not like having a reference type as a member.
    struct value_type
    {
        value_type(inner_difference_type f, inner_reference s)
            : first(f), second(s) { }

        inner_difference_type first;
        inner_reference       second;
    };

    typedef std::forward_iterator_tag iterator_category;
    typedef inner_difference_type     difference_type;
    typedef value_type&               reference;
    typedef value_type*               pointer;

    explicit enumerating_iterator(inner_iterator it = inner_iterator(), 
                                  difference_type index = 0) 
        : it_(it), index_(index) { }

    enumerating_iterator& operator++() 
    {
        ++index_;
        ++it_;
        return *this;
    }

    enumerating_iterator operator++(int)
    {
        enumerating_iterator old_this(*this);
        ++*this;
        return old_this;
    }

    const value_type& operator*() const 
    { 
        value_.reset(value_type(index_, *it_));
        return value_.get();
    }

    const value_type* operator->() const { return &**this; }

    friend bool operator==(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return lhs.it_ == rhs.it_;
    }

    friend bool operator!=(const enumerating_iterator& lhs,
                           const enumerating_iterator& rhs)
    {
        return !(lhs == rhs);
    }

private:

    // Ensure that the template argument passed to IteratorT is a forward
    // iterator; if template instantiation fails on this line, IteratorT is
    // not a valid forward iterator:
    typedef typename std::enable_if<
        std::is_base_of<
            std::forward_iterator_tag,
            typename std::iterator_traits<IteratorT>::iterator_category
        >::value
    >::type ensure_iterator_t_is_a_forward_iterator;

    inner_iterator it_;              //< The current iterator
    difference_type index_;          //< The index at the current iterator
    mutable lazy<value_type> value_; //< Pair to return from op* and op->
};

// enumerating_iterator<T> construction type deduction helpers
template <typename IteratorT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it)
{
    return enumerating_iterator<IteratorT>(it);
}

template <typename IteratorT, typename DifferenceT>
enumerating_iterator<IteratorT> make_enumerator(IteratorT it, DifferenceT idx)
{
    return enumerating_iterator<IteratorT>(it, idx);
}

这已经过最低限度的测试;如果我删除C ++ 0x类型特征和#include <algorithm> #include <array> #include <iostream> struct print_pair { template <typename PairT> void operator()(const PairT& p) { std::cout << p.first << ": " << p.second << std::endl; } }; int main() { std::array<float, 5> data = { 1, 3, 5, 7, 9 }; std::for_each(make_enumerator(data.begin()), make_enumerator(data.end()), print_pair()); } (我在这台笔记本电脑上没有更新版本的g ++来测试),Comeau和g ++ 4.1都会接受它。如果您发现任何错误,请告诉我。

我对如何改善这一点的建议非常感兴趣。具体来说,我想知道是否有办法使用aligned_storage,无论是使用Boost中的东西还是修改迭代器本身。我希望我只是愚蠢而且实际上有一种非常简单的方法可以更清洁地实现它。

答案 6 :(得分:2)

wrap iterators for enumerate的另一种方式:

必填标题:

#include <algorithm>
#include <iterator>
#include <utility>

包装迭代器:

template<class Iter, class Offset=int>
struct EnumerateIterator : std::iterator<std::input_iterator_tag, void, void, void, void> {
  Iter base;
  Offset n;

  EnumerateIterator(Iter base, Offset n = Offset()) : base (base), n (n) {}

  EnumerateIterator& operator++() { ++base; ++n; return *this; }
  EnumerateIterator operator++(int) { auto copy = *this; ++*this; return copy; }

  friend bool operator==(EnumerateIterator const& a, EnumerateIterator const& b) {
    return a.base == b.base;
  }
  friend bool operator!=(EnumerateIterator const& a, EnumerateIterator const& b) {
    return !(a == b);
  }

  struct Pair {
    Offset first;
    typename std::iterator_traits<Iter>::reference second;

    Pair(Offset n, Iter iter) : first (n), second(*iter) {}

    Pair* operator->() { return this; }
  };

  Pair operator*() { return Pair(n, base); }
  Pair operator->() { return Pair(n, base); }
};

枚举重载:

template<class Iter, class Func>
Func enumerate(Iter begin, Iter end, Func func) {
  typedef EnumerateIterator<Iter> EI;
  return std::for_each(EI(begin), EI(end), func);
}
template<class T, int N, class Func>
Func enumerate(T (&a)[N], Func func) {
  return enumerate(a, a + N, func);
}
template<class C, class Func>
Func enumerate(C& c, Func func) {
  using std::begin;
  using std::end;
  return enumerate(begin(c), end(c), func);
}

詹姆斯的复制测试:

#include <array>
#include <iostream>

struct print_pair {
  template<class Pair>
  void operator()(Pair const& p) {
    std::cout << p.first << ": " << p.second << "\n";
  }
};

int main() {
  std::array<float, 5> data = {1, 3, 5, 7, 9};
  enumerate(data, print_pair());
  return 0;
}

我不包括在这里提供抵消;虽然它在EnumerateIterator中完全准备好以0以外的方式启动。左边的选择是做出偏移的类型以及是否为额外参数添加重载或使用默认值。 (没有理由偏移必须是迭代器的差异类型,例如,如果你使它与某个日期相关的类型,每次迭代对应于第二天怎么办?)

答案 7 :(得分:1)

遵循C和C ++的标准约定,第一个元素的索引为0,最后一个元素的索引大小为() - 1。

所以你必须做以下事情; -

std::vector<float> data;
int index = 0;

data.push_back(1.0f);
data.push_back(1.0f);
data.push_back(2.0f);

// lambda expression
std::for_each(data.begin(), data.end(), [&index](float value) {
// Can I get here index of the value too?
   cout<<"Current Index :"<<index++; // gets the current index before increment
});

答案 8 :(得分:0)

也许在lambda函数中,传递一个int&而不是值int,所以你有地址。 &安培;然后你可以用它来推断你在第一个项目中的位置

那会有用吗?我不知道for_each是否支持引用

答案 9 :(得分:0)

您还可以将结构作为第三个参数传递给std :: for_each,并像这样计算索引:

struct myStruct {
   myStruct(void) : index(0) {};
   void operator() (float i) { cout << index << ": " << i << endl; index++; }
   int index;
};

int main()
{

   std::vector data;
   data.push_back(1.0f);
   data.push_back(4.0f);
   data.push_back(8.0f);

   // lambda expression
   std::for_each(data.begin(), data.end(), myStruct());

   return 0;
}