Подтвердить что ты не робот

Как мне вернуться из функции внутри лямбда?

Рассмотрим следующий код игрушки, чтобы определить, содержит ли диапазон элемент:

template<typename Iter, typename T>
bool contains1(Iter begin, Iter end, const T& x)
{
    for (; begin != end; ++begin)
    {
        if (*begin == x) return true;
    }
    return false;
}

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

Как бы написать то же самое с for_each и лямбдой? Следующее не работает...

template<typename Iter, typename T>
bool contains2(Iter begin, Iter end, const T& x)
{
    std::for_each(begin, end, [&x](const T& y) {
        if (x == y) return true;
    });
    return false;
}

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

Нужно ли мне бросать исключение, чтобы выбраться из лямбды? Опять же, есть, вероятно, десяток лучших решений этой конкретной проблемы, которые вообще не связаны с лямбдами, но это не то, о чем я прошу.

4b9b3361

Ответ 1

Как бы написать то же самое с for_each и лямбдой?

Вы не можете (исключая исключения). Ваша функция не изоморфна циклу for-each (= вид отображения), это так просто.

Вместо этого ваша функция описывается сокращением, поэтому, если вы хотите использовать функции более высокого порядка для ее замены, используйте сокращение, а не карту.

Если у С++ был соответствующий, универсальный reduce, то ваш алгоритм выглядел бы следующим образом:

template<typename Iter, typename T>
bool contains2(Iter begin, Iter end, const T& x)
{
    return stdx::reduce(begin, end, [&x](const T& y, bool accumulator) {
        return accumulator or x == y;
    });
}

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

Увы, С++ не предлагает такую ​​функциональность, насколько я вижу. Theres accumulate, но это не будет короткое замыкание (он не может - С++ не знает, что операция внутри лямбда закорочена, и это не реализован рекурсивно).

Ответ 2

std::for_each - не алгоритм, который вы должны использовать, если хотите закончить цикл раньше. Кажется, вы хотите std::find_if или что-то подобное. Вы должны использовать алгоритм, наиболее подходящий для вашей задачи, а не только тот, с которым вы знакомы.


Если вы действительно, действительно, действительно должны "возвращаться" из алгоритма раньше, вы можете -

Предупреждение: то, что следует, действительно, очень плохая идея, и вы никогда не должны этого делать. В самом деле, глядя на код, вы можете расплавить свое лицо. Вы были предупреждены!

Выбросить исключение:

bool contains2(Iter begin, Iter end, const T& x)
{
  try {
    std::for_each(begin, end, [&x](const T& y) {
        if (x == y)
          throw std::runtime_error("something");
    });
  }
  catch(std::runtime_error &e) {
    return true;
  }
  return false;
}

Ответ 3

Lambdas - неправильный уровень абстракции, потому что они ведут себя в основном как функции - по крайней мере, когда дело доходит до контроля потока, что здесь важно. Вы не хотите, чтобы что-то было "инкапсулировано" как функция (или процедуры процедурного программирования), которая может на С++ только прямое возвращение или исключение. Любая попытка подорвать это поведение следует считать патологическим, по моему мнению, или, по крайней мере, не должна маскироваться как процедура.

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

Ответ 4

Использовать собственный алгоритм:

template<class I, class F>
bool aborting_foreach(I first, I last, F f) {
  while(;first!=last;++first) {
    if(!f(*first))
      return false;       
  }
  return true;
}

Хорошо, это на самом деле std:: all_of, но вы получаете эту идею. (См. "Ответ на ответ" ). Если вашей функции необходимо вернуть какой-либо тип, вы можете использовать какой-то вариант:

// Optional A value
template<class A>
class maybe {
  // ...
};

или

// Stores either a A result of a B "non local return"
template<class A, class B>
class either {
  …
};

См. соответствующие типы Haskell. Вы можете использовать С++ 01 "unrestricted union", чтобы реализовать это чисто.

Чистый способ сделать нелокальный выход, использует продолжения, но у вас их нет в С++.

Ответ 5

Используйте std:: any_of.

template<typename Iter, typename T>
bool contains2(Iter begin, Iter end, const T& x)
{
    const bool contains = std::any_of(begin, end, [&x](const T& y)
    {
        return x == y;
    });

    return contains;
}

Ответ 6

В этом контексте лямбда подобна любой другой функции, которая вызывается из данной функции contains2(). Возврат из другой функции не означает, что вы возвращаетесь от данной функции. Таким образом, это невозможно и то, как дизайн должен идти.

Для паттернов, подобных приведенному примеру, бросание исключения - лишние накладные расходы. Я бы установил переменную bool внутри лямбды вместо return (а также установить begin = end;). Этот bool можно проверить для возврата из заданной функции contains2().

Ответ 7

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

template<typename Iter, typename T> 
bool contains2(Iter begin, Iter end, const T& x) 
{ 
    bool tContains = false;
    std::for_each(begin, end, [&](const T& y) mutable { 
        tContains = tContains || x == y; 
    });
    return tContains; 
}