На основе диапазона для и других приращений - PullRequest
4 голосов
/ 14 июня 2019

Допустим, нам нужно перебрать контейнер.Традиционный цикл for будет выглядеть следующим образом:

for (auto it = container.begin(), end = container.end();
     it != end; 
     ++it)
{
    doStuff(*it);
}

, тогда как основанный на диапазоне for будет выглядеть так:

for (auto& element : container)
{
    doStuff(element);
}

Теперь, в какой-то момент разработки мыПоймите, что по тем или иным причинам нам нужно увеличить что-то еще за эти итерации цикла.

То, что нужно увеличивать, может быть различным.Например, если у нас есть соответствующие данные, хранящиеся в других контейнерах того же размера, нам может понадобиться увеличивать итераторы в этих контейнерах также по мере прохождения итераций (хотя я надеюсь, что будущая версия стандартной библиотеки позволит нам сделать это большевыразительно, через структурированные привязки и стандартную версию boost :: range :: Объединение или что-то), то, что нужно увеличить, это просто счетчик.


Традиционный цикл теперь будет выглядеть так:

unsigned int elementID = 0u;
for (auto it = container.begin(), end = container.end();
     it != end;
     ++it, ++elementID)
{
    doStuff(*it, elementID);
}

Едва ли что-либо должно быть изменено, и добавление ++elementID после ++it гарантирует, что счетчик будет увеличиваться независимо от того, что после каждой итерации.Даже если бы другой программист изменил тело цикла и, скажем, рано переходил к следующей итерации при определенных условиях через continue, не было бы никакого риска, что он забыл бы увеличить счетчик или что-то в этом роде.


Теперь, на основе диапазона, насколько я могу судить, единственный способ сделать приращение - сделать что-то вроде этого:

unsigned int elementID = 0u;
for (auto& element : container)
{
    doStuff(element, elementID);
    ++elementID;
}

То есть, чтобы поместить приращение в тело цикла.

Это менее выразительно по отношению к elementID (то есть, если тело кода длинное, кто-то, читающий код, не сразу увидит, чтомы тоже перебираем elementID), и это не дает гарантии, о которой я говорил выше, поэтому оно также подвержено ошибкам.

Нет ли другого способа реализовать это с помощьюдиапазон на основе?Или есть способ написать что-то вроде for(auto& element : container; ++elementID){...}, о котором я просто не знаю?


Редактировать после того, как люди ответили

Невинпредложенный Boost BOOST_SCOPE_EXIT_ALL, который наиболее близок к тому, что я имел в виду, когда речь идет о неродных решениях.

Я не уверен насчет фактической реализации, но я полагаю, что это зависит от лямбд и деструкторов.Я написал это, чтобы проверить это:

template <typename T>
class ScopeExitManager
{
public:
    ScopeExitManager(T const& functionToRunOnExit) : _functionToRunOnExit(functionToRunOnExit)
    {
    }

    ~ScopeExitManager()
    {
        _functionToRunOnExit();
    }

private:
    T _functionToRunOnExit;
};


template <typename T>
ScopeExitManager<T> runOnScopeExit(T const& functionToRunOnExit)
{
    return {functionToRunOnExit};
}

Что позволило мне написать что-то вроде:

unsigned int elementID = 0u;
for (auto& element : container)
{
    // Always at the beginning of the loop
    auto scopeExitManager = runOnScopeExit([&elementID](){++elementID;});

    // Actual body of the loop
    doStuff(element, elementID);
}

, который выразителен и гарантирует, что elementID будет увеличен,Это здорово!

Ответы [ 3 ]

5 голосов
/ 14 июня 2019

Еще один способ сделать это - использовать что-то вроде Boost.ScopeExit , например:

unsigned int elementID = 0u;
for (auto& element : container)
{
    BOOST_SCOPE_EXIT_ALL(&) { ++elementID; };
    doStuff(element, elementID);
}
4 голосов
/ 14 июня 2019

Нет, синтаксис не допускает больше, чем оператор init (начиная с C ++ 20).

Технически, вы можете встраивать дополнительные операторывыполняется во время сравнения и / или увеличения итератора внутри range_expression .Это можно сделать, создав универсальный класс-обертку для контейнеров, реализовав свои собственные итераторы в терминах предоставленных контейнеров + дополнительная логика:

auto additional_condition = [](auto const& element) { return ...; };
auto increment_statement = []() { ... };

for(auto& element :
    custom_iterable(container, additional_condition, increment_statement))
{
    ...
}

Но это в значительной степени противоречит цели на основе диапазонадля (более простой синтаксис).Классический цикл for не так уж и плох.

Давайте не будем забывать макросы - они, вероятно, способны:

  1. повторно реализовать основанный на диапазоне диапазон для
  2. addбольше аргументов, разделенных запятыми

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

1 голос
/ 15 июня 2019

С range-v3 (который должен быть частью C ++ 20) вы можете сделать что-то вроде:

for (const auto& p : ranges::view::zip(container, ranges::view::ints)) {
    doStuff(p.first /* element*/ , p.second /*ID*/);
}

Демо

...