2011-09-03 15 views
12

Consideremos el siguiente código de juguete para determinar si un rango contiene un elemento:¿Cómo regreso desde una función dentro de una 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; 
} 

(Sí, lo sé, ya hay perfectamente bien algoritmos en la biblioteca estándar, que no es el punto.)

Cómo ¿Escribiría lo mismo con for_each y una lambda? Lo siguiente no funciona ...

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

... porque eso solo volvería de la lambda, no de la función.

¿Tengo que lanzar una excepción para salir de la lambda? Nuevamente, hay probablemente una docena de mejores soluciones para este problema específico que no involucran a lambdas en absoluto, pero eso no es lo que estoy pidiendo.

+1

No se puede volver de esta manera lambda. Lambda es, para el compilador, otra función, se puede pasar a otro lugar. Sería bastante tonto pasar lambda a otro método, donde su llamada saltaría 2 niveles, ¿no? – nothrow

+3

Realmente no debería usar for_each si no desea procesar todos los elementos. –

+1

No puedes hacer esto. Puede lograr el mismo efecto de muchas otras maneras. ¿Tiene un ejemplo no artificial en el que realmente valdría la pena? – Mankarse

Respuesta

7

¿Cómo podría escribir lo mismo con for_each y una lambda?

No se puede (dejando a un lado las excepciones). Su función no es isomórfica para un bucle for-each (= tipo de mapeo), es tan simple como eso.

En cambio, su función se describe por una reducción así que si desea usar funciones de orden superior para reemplazarla, use una reducción, no un mapa.

Si C++ tenían una respuesta adecuada, de propósito general reduce entonces su algoritmo se vería de la siguiente manera:

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

Por supuesto, esto sólo salidas anticipadas si la reducción se especializó correctamente para valores de resultado booleanas , para cortocircuitar.

Ay, C++ no ofrece una funcionalidad como lo que veo. Hay accumulate pero eso no provocará un cortocircuito (no puede - C++ no sabe que la operación dentro de la lambda está en cortocircuito, y no se implementa recursivamente).

+0

se necesitaría tipos perezosos si quieres un genérico reducir aplicación a corto circuito. – ysdx

+0

@ysdx Puedes especializar un rasgo para el functor ('is_short_circuited'). No es tan elegante, pero tiene un propósito general. –

0

En este contexto, lambda es como cualquier otra función que se llama desde la función contains2() dada. Regresar de otra función no significa que regresa de la función dada. Por lo tanto, esto es no es posible y así es como debería ser el diseño.

Para los patrones como el ejemplo dado, lanzar una excepción es una sobrecarga innecesaria. Establecería una variable bool dentro de la lambda en lugar de return (y también establecería begin = end;). Se puede verificar que bool regrese desde la función dada contains2().

+0

Excepto que establecer un bool significa que el resto de la secuencia se repetirá, mientras que lanzar una excepción terminaría la iteración. Dependiendo del tamaño de la secuencia, lanzar una excepción probablemente sea más rápido (nuevamente, dudo que el rendimiento sea importante, ya que obviamente es solo un ejemplo de "qué pasaría si". – jalf

+0

@jalf, también podemos configurar 'begin = end ; 'dentro de la función lambda para evitar iterar el resto de la secuencia. – iammilind

+0

Puedes intentarlo, pero eso supone que' for_each' no copiará esos iteradores internamente. – jalf

7

std::for_each no es el algoritmo que debe usar si desea finalizar el ciclo antes de tiempo. Parece que quieres std::find_if o algo similar. Debe usar el algoritmo que sea más apropiado para su tarea, no solo el que conoce.


Si realmente, realmente , realmente debe "volver" de un algoritmo temprano, usted puede-

Advertencia: lo que sigue es un muy, muy mala idea y prácticamente nunca debes hacerlo. De hecho, mirar el código puede derretir tu rostro. ¡Has sido advertido!

lanzar una excepción:

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

Básicamente estás volviendo a formular la pregunta sin el signo de interrogación. * dijo que sabe que hay más apropiadas 'algoritmos std', y mencionó la posibilidad de lanzar una excepción. – jalf

+1

-1 lo JALF dijo – IronMensan

2

Lambdas son el nivel de abstracción equivocada porque se comportan en gran medida como funciones - al menos cuando se trata de controlar el flujo, que es lo que importa aquí. No desea que algo esté "encapsulado" como una función (o los procedimientos de programación de procedimientos), que en C++ solo puede devolver directamente o lanzar una excepción. Cualquier intento de subvertir este comportamiento debe considerarse patológico en mi opinión, o al menos no debe disfrazarse como un procedimiento.

Para un control más detallado del flujo de ejecución, algo así como corutinas podría ser un nivel de abstracción y/o primitivo más adecuado. Aún así, me temo que el resultado final no se parecerá en nada al uso de std::for_each.

2

utilizar un algoritmo personalizado:

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

Ok, esto es, de hecho, std :: all_of pero se entiende la idea. (Ver la "respuesta de reducción"). Si su función debe devolver algún tipo, es posible que desee utilizar algún tipo de variante:

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

o

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

Ver los correspondientes tipos de Haskell. Puede usar C++ 01 "unión no restringida" para implementar esto limpiamente.

Una forma limpia de hacer una salida no local, es usar continuaciones pero no las tiene en C++.

0

Como usted y otros han señalado for_each no es el algoritmo correcto para usar aquí. No hay forma de salir del bucle for_each, salvo la excepción (juego de palabras): debe ejecutarlo completamente.

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

Usar 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; 
} 
Cuestiones relacionadas