如何在lambda函数中从一个函数返回?

22

考虑以下这个玩具代码,用于确定一个范围是否包含一个元素:

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和lambda表达式编写相同的内容?以下方式无法工作...

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;
}
...因为这只会从lambda中返回,而不是从函数中返回。

我必须抛出异常才能从lambda中退出吗?再次说明,可能有十几种更好的解决方案来解决这个特定的问题,这些解决方案根本不涉及lambda,但这不是我要问的。


1
您不能使用此方式从 lambda 返回。对于编译器来说,Lambda 是另一个函数,可以传递到其他地方。将 Lambda 传递给另一个方法,然后在该方法中调用 Lambda 会跳过两个级别,这相当愚蠢,不是吗? - nothrow
4
如果您不想处理所有元素,就不应该使用for_each函数。 - Bo Persson
1
你不能这样做。有很多其他方法可以达到相同的效果。你有没有一个非人为制造的例子,它真的值得去尝试吗? - Mankarse
2
那个lambda表达式是错误的(我不知道编译器是否会检测并抱怨,但)lambda推断出的类型将提供bool operator()( const T& ),但是您的实现在x!=y时未返回该函数。话虽如此,您想要做的更像是find_if而不是for_each(一旦函数被更正)。 - David Rodríguez - dribeas
1
@Fred,明确一下,你不是使用异常来提前退出lambda。你是使用异常来改变std::for_each的流程控制。忽略函数对象的lambda特性,假设你将一个普通函数传递给for_each。你需要一个函数返回到上面两个级别,而第一个级别不知道它的存在。你是正确的,异常是唯一的方法。但是,你不应该这样做。概念本身没有任何问题,除了C/C++/algol等语言不支持这种语法。你正在滥用异常来创建一种语言特性。 - deft_code
显示剩余5条评论
7个回答

10

如何使用for_each和lambda表达式编写相同的代码?

你不可以(除非有例外情况)。你的函数与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 ++不知道lambda内部的操作是否短路,并且它没有递归实现)。


如果您想实现通用的reduce算法并希望其短路运算,那么您需要使用lazy类型。 - ysdx
@ysdx 你可以为函子专门定义一个特质(is_short_circuited)。这样做不太优雅,但更通用。 - Konrad Rudolph

5

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

9
他基本上只是在重新表述问题,没有问号。他他知道有更合适的“std”算法,并提到了抛出异常的可能性。 - jalf

4
使用 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

使用自定义算法:

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的“不受限制的联合”来清洁地实现此操作。

一种干净的非局部退出方式是使用continuations,但是在C++中您没有它们。


2
Lambda表达式在抽象层面上是错误的,因为它们在控制流方面的行为与函数非常相似,而这正是重要的。您不希望像函数(或过程式编程的过程)那样“封装”,在C++中只能直接返回或抛出异常。我认为,任何试图颠覆这种行为的尝试都应该被视为病态,或者至少不应该伪装成一个过程。
为了更细粒度地控制执行流程,类似协程的东西可能是更适合的抽象层次和/或原语。然而,我担心最终结果看起来与使用std::for_each完全不同。

1
正如你和其他人指出的那样,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
在这个上下文中,lambda函数就像任何其他从给定函数contains2()调用的函数一样。从其他函数返回并不意味着你正在从给定函数返回。因此,这是不可能的,这正是设计应该走的方向。
对于类似于给定示例的模式,抛出异常是不必要的开销。我会在lambda内部设置一个bool变量,而不是使用return(还要设置begin = end;)。可以检查这个bool是否从给定函数contains2()返回。

除了设置布尔值意味着其余序列将被迭代,而抛出异常将结束迭代。根据序列的大小,抛出异常可能会更快(再次,我怀疑性能是否重要,因为这显然只是一个玩具“假设”示例)。 - jalf
@jalf,我们也可以在 lambda 函数内部设置 begin = end; 以避免迭代序列的其余部分。 - iammilind
你可以尝试。但这假设for_each不会在内部复制那些迭代器。 - jalf

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接