2011-09-03 58 views
12

考虑下面的玩具代码,以确定范围是否包含一个元素:如何从lambda函数中返回?

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传递给另一个方法会非常愚蠢,在这种方法中,调用会跳到2个级别,不是吗? – nothrow

+3

如果你不想处理所有的元素,你真的不应该使用for_each。 –

+1

你不能这样做。您可以通过许多其他方式实现相同的效果。你有没有一个没有人为的例子,它实际上是值得的? – Mankarse

回答

7

我该怎么写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是短路的,它不是递归实现的)。

+0

如果您希望通用的reduce实现短路,则需要使用惰性类型 – ysdx

+0

@ysdx你可以专门研究函数的特征('is_short_circuited'),不是很优雅,而是一般用途。 –

0

在这种情况下,lambda就像从给定函数contains2()调用的任何其他函数一样。从其他函数返回并不意味着你从给定函数返回。因此,这是不可能这就是设计应该如何去。

对于像给定例子那样的模式,抛出异常是不必要的开销。我会在lambda表中设置一个bool变量,而不是return(也可以设置begin = end;)。这个bool可以检查从给定函数contains2()返回。

+0

除了设置布尔意味着其余的序列将被迭代,而抛出异常将结束迭代。根据序列的大小,抛出一个异常可能会更快(然后再次,我怀疑性能问题,因为这显然只是一个玩具“假设”的例子。) – jalf

+0

@jalf,我们也可以设置'begin = end ;'在lambda函数内部避免重复序列的其余部分 – iammilind

+0

你可以尝试,但是假设'for_each'不会在内部复制这些迭代器 – jalf

7

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

你基本上只是在没有问号的情况下重新解释这个问题,他* *他知道有更合适的'std'算法,并且他提到了抛出异常的可能性。 – jalf

+1

-1什么是jalf表示 – IronMensan

2

Lambda表达式是错误的抽象级别,因为他们的行为在很大程度上类似的功能 - 至少当它涉及到控制流,这是这里最重要的。你不想把某些东西当作一个函数(或程序编程的程序)“封装”,它只能在C++中直接返回或抛出异常。任何颠覆这种行为的企图都应该被认为是病态的 - 或者至少不应该伪装成一种程序。

对于执行流程的细粒度控制,类似于协程的东西可能是更适合的抽象级别和/或基元。尽管如此,恐怕最终的结果看起来不会像使用std::for_each

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

好吧,其实这是性病:: 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 { 
    … 
}; 

请参见相应的哈斯克尔类型。你可以使用C++ 01“unrestricted union”来实现这一点。

干净的方式做非本地退出,使用continuations,但你没有他们在C + +。

0

正如你和其他人指出的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; 
} 
1

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