Предположим, у меня есть следующий код:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Могу ли я найти положение elem
в векторе, не поддерживая отдельный итератор?
Предположим, у меня есть следующий код:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Могу ли я найти положение elem
в векторе, не поддерживая отдельный итератор?
Да, вы можете, просто возьмите немного массажа;)
Трюк состоит в том, чтобы использовать композицию: вместо того, чтобы перебирать контейнер непосредственно, вы "застегиваете" его индексом по пути.
Специализированный код на молнии:
template <typename T>
struct iterator_extractor { typedef typename T::iterator type; };
template <typename T>
struct iterator_extractor<T const> { typedef typename T::const_iterator type; };
template <typename T>
class Indexer {
public:
class iterator {
typedef typename iterator_extractor<T>::type inner_iterator;
typedef typename std::iterator_traits<inner_iterator>::reference inner_reference;
public:
typedef std::pair<size_t, inner_reference> reference;
iterator(inner_iterator it): _pos(0), _it(it) {}
reference operator*() const { return reference(_pos, *_it); }
iterator& operator++() { ++_pos; ++_it; return *this; }
iterator operator++(int) { iterator tmp(*this); ++*this; return tmp; }
bool operator==(iterator const& it) const { return _it == it._it; }
bool operator!=(iterator const& it) const { return !(*this == it); }
private:
size_t _pos;
inner_iterator _it;
};
Indexer(T& t): _container(t) {}
iterator begin() const { return iterator(_container.begin()); }
iterator end() const { return iterator(_container.end()); }
private:
T& _container;
}; // class Indexer
template <typename T>
Indexer<T> index(T& t) { return Indexer<T>(t); }
И используя его:
#include <iostream>
#include <iterator>
#include <limits>
#include <vector>
// Zipper code here
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto p: index(v)) {
std::cout << p.first << ": " << p.second << "\n";
}
}
Вы можете видеть его на ideone, хотя ему не хватает поддержки цикла for-range, поэтому он менее симпатичный.
EDIT:
Просто вспомнил, что я должен проверять Boost.Range чаще. К сожалению, диапазон zip
не найден, но я нашел perl: boost::adaptors::indexed
. Однако для доступа к итератору требуется доступ к индексу. Позор: x
В противном случае с counting_range
и общим zip
я уверен, что можно было бы сделать что-то интересное...
В идеальном мире я бы предположил:
int main() {
std::vector<int> v{1, 2, 3, 4, 5, 6, 7, 8, 9};
for (auto tuple: zip(iota(0), v)) {
std::cout << tuple.at<0>() << ": " << tuple.at<1>() << "\n";
}
}
С zip
автоматически создавая представление в виде набора кортежей ссылок и iota(0)
просто создавая "ложный" диапазон, который начинается с 0
и просто отсчитывается до бесконечности (или, ну, максимум его типа...).
jrok прав: диапазоны для циклов не предназначены для этой цели.
Однако в вашем случае его можно вычислить с помощью арифметики указателя, так как vector
сохраняет свои элементы смежно (*)
vector<int> list;
for(auto& elem:list) {
int i = elem;
int pos = &elem-&list[0]; // pos contains the position in the vector
// also a &-operator overload proof alternative (thanks to ildjarn) :
// int pos = addressof(elem)-addressof(list[0]);
}
Но это, очевидно, плохая практика, так как она запутывает код и делает его более хрупким (он легко ломается, если кто-то меняет тип контейнера, перегружает оператор &
или заменяет "auto &" на "auto". отлаживать это!)
ПРИМЕЧАНИЕ. Непрерывность гарантируется для вектора в С++ 03, а массив и строка в стандарте С++ 11.
Нет, вы не можете (по крайней мере, не без усилий). Если вам нужна позиция элемента, вы не должны использовать диапазон. Помните, что это просто удобный инструмент для наиболее распространенного случая: выполните код для каждого элемента. В менее распространенных случаях, когда вам нужно положение элемента, вы должны использовать менее удобный обычный цикл for
.
Если у вас есть компилятор с поддержкой С++ 14, вы можете сделать это в функциональном стиле:
#include <iostream>
#include <string>
#include <vector>
#include <functional>
template<typename T>
void for_enum(T& container, std::function<void(int, typename T::value_type&)> op)
{
int idx = 0;
for(auto& value : container)
op(idx++, value);
}
int main()
{
std::vector<std::string> sv {"hi", "there"};
for_enum(sv, [](auto i, auto v) {
std::cout << i << " " << v << std::endl;
});
}
Работает с clang 3.4 и gcc 4.9 (не с 4.8); для обоих нужно установить -std=c++1y
. Причина, по которой вам нужен С++ 14, - это параметры auto
в лямбда-функции.
Основываясь на ответе @Matthieu, есть очень элегантное решение, использующее упомянутый выше boost :: adapters :: indexed:
std::vector<std::string> strings{10, "Hello"};
int main(){
strings[5] = "World";
for(auto const& el: strings| boost::adaptors::indexed(0))
std::cout << el.index() << ": " << el.value() << std::endl;
}
Это работает почти так же, как упомянутое "идеальное мировое решение", имеет красивый синтаксис и является лаконичным. Обратите внимание, что тип el
в этом случае является чем-то вроде boost::foobar<const std::string&, int>
, поэтому он обрабатывает ссылку там и копирование не производится. Это даже невероятно эффективно: https://godbolt.org/g/e4LMnJ (код эквивалентен хранению собственной переменной-счетчика, которая настолько хороша, насколько это возможно)
Для полноты альтернативы:
size_t i = 0;
for(auto const& el: strings) {
std::cout << i << ": " << el << std::endl;
++i;
}
Или используя непрерывное свойство вектора:
for(auto const& el: strings) {
size_t i = &el - &strings.front();
std::cout << i << ": " << el << std::endl;
}
Первый генерирует тот же код, что и версия адаптера буста (оптимально), а последний на 1 инструкцию длиннее: https://godbolt.org/g/nEG8f9
Примечание: если вы хотите знать, есть ли у вас последний элемент, который вы можете использовать:
for(auto const& el: strings) {
bool isLast = &el == &strings.back();
std::cout << isLast << ": " << el << std::endl;
}
Это работает для каждого стандартного контейнера, но необходимо использовать auto&
/auto const&
(как описано выше), но это рекомендуется в любом случае. В зависимости от входных данных это также может быть довольно быстро (особенно, когда компилятор знает размер вашего вектора)
Замените &foo
на std::addressof(foo)
чтобы быть в безопасности для универсального кода.
Если вы настаиваете на использовании диапазона, основанного на индексе, и знаете индекс, довольно просто поддерживать индекс, как показано ниже. Я не думаю, что есть более чистое/более простое решение для диапазонов, основанных на циклах. Но почему бы не использовать стандарт для (;;)? Вероятно, это сделает ваше намерение и код самым ясным.
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
Существует удивительно простой способ сделать это
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
где i
будет вашим обязательным индексом.
Это использует тот факт, что векторы С++ всегда смежны.
Я прочитал из ваших комментариев, что одной из причин, по которой вы хотите знать индекс, является знать, является ли этот элемент первым/последним в последовательности. Если это так, вы можете сделать
for(auto& elem:list) {
// loop code ...
if(&elem == &*std::begin(list)){ ... special code for first element ... }
if(&elem == &*std::prev(std::end(list))){ ... special code for last element ... }
// if(&elem == &*std::rbegin(list)){... (C++14 only) special code for last element ...}
// loop code ...
}
EDIT: Например, это означает, что контейнер пропускает разделитель в последнем элементе. Работает для большинства контейнеров, которые я могу себе представить (включая массивы), (онлайн-демонстрация http://coliru.stacked-crooked.com/a/9bdce059abd87f91):
#include <iostream>
#include <vector>
#include <list>
#include <set>
using namespace std;
template<class Container>
void print(Container const& c){
for(auto& x:c){
std::cout << x;
if(&x != &*std::prev(std::end(c))) std::cout << ", "; // special code for last element
}
std::cout << std::endl;
}
int main() {
std::vector<double> v{1.,2.,3.};
print(v); // prints 1,2,3
std::list<double> l{1.,2.,3.};
print(l); // prints 1,2,3
std::initializer_list<double> i{1.,2.,3.};
print(i); // prints 1,2,3
std::set<double> s{1.,2.,3.};
print(s); // print 1,2,3
double a[3] = {1.,2.,3.}; // works for C-arrays as well
print(a); // print 1,2,3
}
Вот решение на основе макросов, которое, вероятно, превосходит большинство других по простоте, времени компиляции и качеству генерации кода:
#include <iostream>
#define fori(i, ...) if(size_t i = -1) for(__VA_ARGS__) if(i++, true)
int main() {
fori(i, auto const & x : {"hello", "world", "!"}) {
std::cout << i << " " << x << std::endl;
}
}
Результат:
$ g++ -o enumerate enumerate.cpp -std=c++11 && ./enumerate
0 hello
1 world
2 !
Тобиас Видлунд написал хороший перечисляемый заголовок только в стиле Python для MIT (хотя С++ 17):
Действительно приятно использовать:
std::vector<int> my_vector {1,3,3,7};
for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}
Если вы хотите избежать необходимости писать вспомогательную функцию, имея переменная индекса, локальная для цикла, вы можете использовать лямбду с изменяемой переменной.:
int main() {
std::vector<char> values = {'a', 'b', 'c'};
std::for_each(begin(values), end(values), [i = size_t{}] (auto x) mutable {
std::cout << i << ' ' << x << '\n';
++i;
});
}