Как получить индекс значения в векторе, используя for_each?
У меня есть следующий код (компилятор: MSVС++ 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?
});
В приведенном выше фрагменте кода я хочу получить индекс значения в векторе данных внутри выражения лямбда. Кажется, for_each принимает только одну функцию параметра. Есть ли альтернатива этому, используя for_each и лямбда?
Ответы
Ответ 1
Я не думаю, что вы можете захватить индекс, но вы можете использовать внешнюю переменную для индексирования, захватив ее в лямбда:
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;
});
Ответ 2
Что-то вроде этого:
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;
});
Ответ 3
Я думаю, что самый простой способ - использовать std::accumulate
:
std::accumulate(data.begin(), data.end(), 0, [](int index, float const& value)->int{
...
return index + 1;
});
Это решение работает с любым контейнером, и для него не требуются переменные или пользовательские классы.
Ответ 4
В С++ 14 благодаря обобщенным лямбда-захватам вы можете сделать что-то вроде этого:
std::vector<int> v(10);
std::for_each(v.begin(), v.end(), [idx = 0] (int i) mutable {
// your code...
++idx; // 0, 1, 2... 9
});
Ответ 5
В качестве альтернативы вы можете использовать & value - & data [0], хотя это может быть немного дороже.
std::for_each(data.begin(), data.end(), [&data](float const& value) {
int idx = &value - &data[0];
});
Ответ 6
Роджер Пате предложил в комментарии к моему другому ответу создать оболочку итератора, которая выполняет перечисление. Реализация этого была немного избитой.
Эта оболочка итератора принимает форвардный итератор, тип значения которого T
(называемый "внутренним итератором" ) и преобразует его в форвардный итератор, тип значения которого равен pair<int, T&>
, где int
- тип расстояния внутренний итератор.
Это было бы довольно просто, за исключением двух вещей:
- Конструктор
std::pair
принимает свои аргументы по ссылке const, поэтому мы не можем инициализировать элемент данных типа T&
; мы должны будем создать собственный тип пары для итератора.
- Чтобы поддерживать правильную семантику для итератора, нам нужно lvalue (
operator*
необходимо вернуть ссылку, а operator->
- вернуть указатель), поэтому пара должна быть членом данных итератора, Поскольку он содержит ссылку, нам понадобится способ "reset", и нам понадобится его, чтобы он был лениво инициализирован, чтобы мы могли корректно обрабатывать концевые итераторы. 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);
}
Тест-заглушка:
#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());
}
Это было минимально проверено; Comeau и g++ 4.1 оба принимают его, если я удаляю черты типа С++ 0x и aligned_storage
(у меня нет новой версии g++ на этом ноутбуке для тестирования). Пожалуйста, дайте мне знать, если вы найдете какие-либо ошибки.
Мне очень интересны предложения о том, как улучшить это. В частности, мне бы хотелось знать, есть ли способ использовать lazy<T>
, либо используя что-то из Boost, либо изменяя сам итератор. Надеюсь, я просто тупой и что на самом деле действительно простой способ реализовать это более чисто.
Ответ 7
Другой способ обернуть итераторы для перечисления:
Обязательные заголовки:
#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. Выбор слева - это какой тип сделать смещение и добавить ли лишние значения для дополнительного параметра или использовать значение по умолчанию. (Нет причин, по которым смещение должно быть типом разности итераторов, например, что, если вы сделали его связанным с датой типа, с каждой итерацией, соответствующей следующему дню?)
Ответ 8
Следуя стандартным соглашениям для 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
});
Ответ 9
возможно, в лямбда-функции, передайте ей int&
вместо значения int, поэтому у вас будет адрес. и затем вы можете использовать это, чтобы вывести свою позицию из первого элемента.
будет работать? я не знаю, поддерживает ли for_each ссылки
Ответ 10
Вы также можете передать структуру как третий аргумент в 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;
}