LINUX.ORG.RU

Внешний функтор/функция для итерации по контейнеру

 , ,


0

6

Задача такова: есть один едиственный контейнер с данными, есть несколько способов по нему пройтись, но для каждой итерации надо выполнять одинаковый код. Другими словами, нужно иметь возможность задавать алгоритм итерирования извне. Однако то, что выполняется на каждой итерации — должно быть внутренним.

Можно использовать все фичи C++17.

Для себя наклепал вот такой рабочий пример: https://wandbox.org/permlink/kQc59EAs5MSMCxTE

Поревьювьте код, пожалуйста.

using Storage = std::vector<std::string_view>;
using PointType = Storage::const_iterator;

// пример функции итерирования, которая должна вызывать коллбек для каждой итерации
// template для дедукции лямбды
template <typename Callback>
struct IterateArrivals {
  void operator()(const Storage &storage, Callback callback) const {
    for (auto pointBegin = storage.rbegin(); pointBegin != storage.rend(); pointBegin += 2) {
      callback(pointBegin.base() - 1, pointBegin.base() - 2);
    }
  }
};

namespace {
// хелпер, чтоб задедуктить тип лямбды (его не можно задать explicit)
template <template <typename...> class Iterate, typename Callback, typename... Args>
void callIterate(Callback&& callback, Args&&... args) {
    Iterate<Callback>{}(std::forward<Args>(args)..., std::forward<Callback>(callback));
}
} // namespace

template <template <typename...> class Iterate>
void print(const Storage &storage) {
  unsigned int count = 0;
  
  // тут выполняем что-то для каждой итерации (лямбду)
  callIterate<Iterate>([&](PointType loc1, PointType loc2) {
    std::cout << '[' << ++count << "] {" << *loc1 << ", " << *loc2 << "}\n";
    return true;
  }, storage);
}

int main() {
  Storage itin{"A", "B", "B", "C", "E", "F"}; 
  print<IterateArrivals>(itin);
  // печатает:
  // [1] {F, E}
  // [2] {C, B}
  // [3] {B, A}
}

Лол :-) Казалось бы, есть STL с её «гибкостью, благодаря итераторам, которые не знают об алгоритмах, не знающих об итераторах». Бери её и обходи свои контейнеры как угодно. Но нет, надо написать никому не известные обёртки-велосипеды. Странно это всё.

По делу. Бери STL и пиши обход напрямую. Желательно, с использованием стандартных алгоритмов по максимуму. Хоть как-то облегчишь читателям их тяжёлую ношу. В конце концов, стандарт для этого и существует, чтобы его знатоки могли без проблем читать стандартный код. Потому как с твоими этими «print<IterateArrivals>(itin);» нихрена не понятно что это и как это. (Не то, что бы это сложно, прочитать такой код, просто это лишняя трата времени.)

azelipupenko ()

Можно просто запилить свой итератор с нужным поведением и использовать стандартные алгоритмы для обхода контенеров.

anonymous ()
Ответ на: комментарий от anonymous

Можно просто запилить свой итератор с нужным поведением и использовать стандартные алгоритмы для обхода контенеров.

Ну вообще да, но там проблема, что функция итерирования передает два итератора (причем второй не обязан быть std::next(первый))

Конечно, можно в кастомный итератор добавить метод .second() который будет возвращать нужный второй итератор.

В любом случае — писать итератор немного больше бойлерплейта, как мне кажется.

KennyMinigun ★★★★★ ()
Ответ на: комментарий от KennyMinigun

В любом случае — писать итератор немного больше бойлерплейта, как мне кажется.

Во, самый правильный вариант - тот, что кажется проще. Если гнаться за идеальным решением, то проект можно никогда не закончить :)

anonymous ()
Ответ на: комментарий от KennyMinigun

писать итератор немного больше бойлерплейта, как мне кажется.

Если немного, то надо писать именно итератор. Потому что все знают, что такое итератор, а о твоем личном коде никто ничего не знает.

tailgunner ★★★★★ ()

Как уже сказали, различные типы итераторов дают нужный результат. А если так, чтоб работало хоть как-то и хрен с ним - ну ты ведь не за этим сюда пришёл

Deleted ()
Ответ на: комментарий от KennyMinigun

Конечно, можно в кастомный итератор добавить метод .second() который будет возвращать нужный второй итератор

А вот этого не надо. end = iterator() же. Его и передавать

Deleted ()
Ответ на: комментарий от Deleted

end = iterator() же. Его и передавать

Не совсем понял идею. Но второй итератор у меня меняется на каждой итерации. Т.е. функция итерации отдает пару итераторов на Storage на каждой итерации.

KennyMinigun ★★★★★ ()
Ответ на: комментарий от KennyMinigun

Зачем нужно вот это:

можно в кастомный итератор добавить метод .second() который будет возвращать нужный второй итератор

? На мой взгляд, лучше формировать итератор конца последовательности там же где и итератор начала(и передавать напрямую). Как формировать - тут уж тебе виднее(один из вариантов я и предложил). Либо я неправильно понял твою идею с second()

Deleted ()
Ответ на: комментарий от Deleted

ему просто нужно получать по два соседних элемента контейнера за одну итерацию, для этого можно создать итератор, который инкрементится не на 1, а на 2, но при этом имеет метод .second(), который возвращает соседний элемент.

anonymous ()
Ответ на: комментарий от anonymous

ему просто нужно получать по два соседних элемента контейнера за одну итерацию, для этого можно создать итератор, который инкрементится не на 1, а на 2, но при этом имеет метод .second(), который возвращает соседний элемент.

Ну конкретно в примере из топика — да. А вообще, нет ограничений на то, что может отдавать функция итерирования. Например может быть перебирание пар в стиле (индексы):

{0,1}
{0,2}
{0,3}
{0,4}
...
{0, N}

KennyMinigun ★★★★★ ()