Припустимо, у мене є такий код:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Чи можу я знайти положення elem
вектора у векторі, не підтримуючи окремого ітератора?
Припустимо, у мене є такий код:
vector<int> list;
for(auto& elem:list) {
int i = elem;
}
Чи можу я знайти положення elem
вектора у векторі, не підтримуючи окремого ітератора?
std::find
якась інша функція надмірного використання. Ви не можете укласти ітератори з вміщених елементів. Чому б не підтримувати ітератор?
for
циклу на основі діапазону це внутрішнє ім'я компілятора, і тому його не можна використовувати у вашому коді. Отже, якщо ви дійсно хочете знати, чи перебуваєте ви на останньому елементі, вам слід скористатися for(;;)
циклом.
Відповіді:
Так, можна, просто потрібно трохи помасажувати;)
Фокус полягає у використанні композиції: замість того, щоб перебирати контейнер безпосередньо, ви "застібаєте" його індексом попутно.
Спеціалізований код на блискавці:
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 , хоча йому не вистачає підтримки циклу для діапазону, тому він менш гарний.
РЕДАГУВАТИ:
Просто згадав, що мені слід частіше перевіряти Boost.Range. На жаль, немає zip
діапазону, але я знайшов perl:boost::adaptors::indexed
. Однак для витягування індексу потрібен доступ до ітератора. Сором: х
В іншому випадку з 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
і просто враховується до нескінченності (або, максимум, свого типу ...).
Indexer
щоб також належним чином приймати та утримувати аргументи rvalue, змінивши тип на тип _container
значення, якщо вихідний аргумент є значенням r std::move
/ і / std::forward
- аргументом у.
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". Удачі налагодити це!)
ПРИМІТКА: Сумісність гарантована для вектора в C ++ 03, а для масиву та рядка у стандарті C ++ 11.
vector
в C ++ 03, так array
і string
в C ++ 11.
&
оператора " Ось для чого std::addressof
. : -]
Ні, не можна (принаймні, не без зусиль). Якщо вам потрібно розташування елемента, не слід використовувати для діапазону. Пам’ятайте, що це найзручніший інструмент для найпоширенішого випадку: виконайте певний код для кожного елемента. У менш поширених обставинах, коли вам потрібно розташування елемента, вам доведеться використовувати менш зручний регулярний for
цикл.
На основі відповіді від @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
by, std::addressof(foo)
щоб бути в безпеці для загального коду.
Якщо у вас є компілятор із підтримкою C ++ 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
. Причина, по якій вам потрібен c ++ 14, полягає у auto
параметрах у лямбда-функції.
std::function
використовує стирання типу, яке є дорогим. Чому б не використовувати, template<typename T, typename Callable> void for_enum(T& container, Callable op)
щоб не платити за стирання типу?
Для цього існує напрочуд простий спосіб
vector<int> list;
for(auto& elem:list) {
int i = (&elem-&*(list.begin()));
}
де i
буде ваш необхідний індекс.
Це використовує той факт, що вектори С ++ завжди суміжні .
Якщо ви наполягаєте на використанні діапазону для і для знання індексу, досить тривіально підтримувати індекс, як показано нижче. Я не думаю, що існує більш чисте / простіше рішення для діапазону на основі циклів. Але справді, чому б не використовувати стандарт для (;;)? Це, мабуть, зробить ваш намір і код найбільш чітким.
vector<int> list;
int idx = 0;
for(auto& elem:list) {
int i = elem;
//TODO whatever made you want the idx
++idx;
}
З ваших коментарів я прочитав, що однією з причин, через яку ви хочете знати індекс, є знання, чи є елемент першим / останнім у послідовності. Якщо так, ви можете це зробити
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
}
&elem
і &*std::prev(std::end(list))
не буде працювати або бути практичним. Я погоджуюсь з іншою відповіддю, що для цього більше підходить ітератор, але все ж.
int i=c.size();
перед циклом і тестом if(--i==0)
.
int i
код був лише прикладом. Я видалю його, щоб уникнути плутанини. Навіть якщо ви використовуєте size
до циклу, вам знадобиться лічильник.
Тобіас Відлунд написав приємний ліцензований MIT заголовок стилю Python, лише перераховуючи (хоча C ++ 17):
Дійсно приємно використовувати:
std::vector<int> my_vector {1,3,3,7};
for(auto [i, my_element] : en::enumerate(my_vector))
{
// do stuff
}
Ось рішення на основі макросів, яке, мабуть, перевершує більшість інших за простотою, часом компіляції та якістю генерації коду:
#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 !
Якщо ви хочете уникнути необхідності писати допоміжну функцію, маючи при цьому змінну індексу, локальну до циклу, ви можете використовувати лямбда з змінною змінною .:
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;
});
}
Ось досить гарне рішення з використанням c ++ 20:
#include <array>
#include <iostream>
#include <ranges>
template<typename T>
struct EnumeratedElement {
std::size_t index;
T& element;
};
auto enumerate(std::ranges::range auto& range)
-> std::ranges::view auto
{
return range | std::views::transform(
[i = std::size_t{}](auto& element) mutable {
return EnumeratedElement{i++, element};
}
);
}
auto main() -> int {
auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
for (auto const [index, element] : enumerate(elements)) {
std::cout << "Element " << index << ": " << element << '\n';
}
}
Основними функціями, які використовуються тут, є діапазони c ++ 20, концепції c ++ 20, змінні лямбди c ++ 11, ініціалізатори захоплення лямбда-зондів c ++ 14 та структуровані прив'язки c ++ 17. Зверніться до cppreference.com, щоб отримати інформацію щодо будь-якої з цих тем.
Зверніть увагу, що element
в структурованому прив'язуванні насправді є посилання, а не копія елемента (не те, що тут це має значення). Це пояснюється тим, що будь-які кваліфікації навколоauto
єдиного впливають на тимчасовий об’єкт, з якого витягуються поля, а не самі поля.
Створений код ідентичний коду, що генерується цим (принаймні, gcc 10.2):
#include <array>
#include <iostream>
#include <ranges>
auto main() -> int {
auto const elements = std::array{3, 1, 4, 1, 5, 9, 2};
for (auto index = std::size_t{}; auto& element : elements) {
std::cout << "Element " << index << ": " << element << '\n';
index++;
}
}
Доказ: https://godbolt.org/z/a5bfxz