Как мне вернуться из функции внутри лямбды? - PullRequest
14 голосов
/ 03 сентября 2011

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

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;
}

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

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

Ответы [ 7 ]

7 голосов
/ 03 сентября 2011

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

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

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

Если C ++ имеет соответствующий универсальный 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;
    });
}

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

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

7 голосов
/ 03 сентября 2011

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;
}
2 голосов
/ 03 октября 2011

Использование 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;
}
2 голосов
/ 03 сентября 2011

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

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.Вы можете использовать «неограниченное объединение» C ++ 01, чтобы реализовать это чисто.

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

2 голосов
/ 03 сентября 2011

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

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

0 голосов
/ 03 сентября 2011

Как вы и другие отметили, 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; 
} 
0 голосов
/ 03 сентября 2011

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

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

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