2011-09-03 4 views
12

Si consideri il seguente codice giocattolo per determinare se un campo contiene un elemento:Come ritorno da una funzione all'interno di 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 so, ci sono algoritmi già perfettamente bene nella libreria standard, non è questo il punto.)

Come scriverei la stessa cosa con for_each e un lambda? Ciò che segue non funziona ...

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

... perché ciò restituirebbe solo dal lambda, non dalla funzione.

Devo fare un'eccezione per uscire dalla lambda? Ancora una volta, ci sono probabilmente una dozzina di soluzioni migliori per questo problema specifico che non coinvolgono affatto lambda, ma non è quello che sto chiedendo.

+1

Non è possibile tornare da lambda in questo modo. Lambda è, per il compilatore, un'altra funzione, può essere passata da qualche altra parte. Sarebbe piuttosto sciocco passare il lambda a un altro metodo, dove la sua chiamata salta di 2 livelli, non è vero? – nothrow

+3

Non dovresti davvero usare for_each se non vuoi elaborare tutti gli elementi. –

+1

Non puoi farlo. Puoi ottenere lo stesso effetto in molti altri modi. Hai un esempio non forzato in cui sarebbe effettivamente utile? – Mankarse

risposta

7

Come scrivere la stessa cosa con for_each e un lambda?

Non è possibile (escluse le eccezioni). La tua funzione non è isomorfa per un ciclo for-each (= tipo di mappatura), è così semplice.

Invece, la funzione è descritta da una riduzione, quindi se si desidera utilizzare le funzioni di ordine superiore per sostituirlo, utilizzare una riduzione, non una mappa.

Se C++ avevano un adeguato, general-purpose reduce allora il vostro algoritmo apparirebbe come segue:

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

Naturalmente, questo solo uscite primi se la riduzione è adeguatamente specializzato per i valori risultato booleano , per cortocircuitare.

Ahimè, C++ non offre tale funzionalità, per quanto vedo. C'è lo accumulate ma questo non andrà in corto circuito (non può - C++ non sa che l'operazione all'interno di lambda è cortocircuitata, e non è implementata in modo ricorsivo).

+0

Avresti bisogno di tipi pigri se vuoi che un generico riduca l'implementazione in cortocircuito – ysdx

+0

@ysdx Potresti specializzare un tratto per il functor ('is_short_circuited'). Non abbastanza elegante ma per scopi generali. –

0

In questo contesto, lambda è esattamente come qualsiasi altra funzione chiamata dalla funzione data contains2(). Il ritorno da un'altra funzione non significa che si sta tornando dalla funzione data. Quindi questo è non possibile ed è così che dovrebbe andare il design.

Per i motivi illustrati nell'esempio fornito, l'eliminazione di un'eccezione non è necessaria. Impostare una variabile bool all'interno della lambda anziché return (e impostare anche begin = end;). Questo bool può essere controllato per il ritorno dalla funzione data contains2().

+0

Tranne che l'impostazione di un bool significa che il resto della sequenza verrebbe ripetuto, mentre il lancio di un'eccezione terminerebbe l'iterazione. A seconda delle dimensioni della sequenza, lanciare un'eccezione potrebbe essere più veloce (anche in questo caso dubito che le prestazioni siano importanti, poiché si tratta ovviamente di un esempio "what-if" di giocattoli: – jalf

+0

@jalf, possiamo anche impostare 'begin = end ; 'all'interno della funzione lambda per evitare il ripetersi del resto della sequenza. – iammilind

+0

Puoi provare. Ma questo presuppone che' for_each' non copi quegli iteratori internamente. – jalf

7

std::for_each non è l'algoritmo da utilizzare se si desidera terminare il ciclo in anticipo. Sembra che tu voglia std::find_if o qualcosa di simile. Dovresti usare l'algoritmo più appropriato per il tuo compito, non solo quello che ti è familiare.


Se davvero, davvero , davvero deve "ritornare" da un algoritmo di anticipo, è CAN

Attenzione: quello che segue è un davvero, davvero cattiva idea e non dovresti virtualmente mai farlo. In effetti, guardando il codice potresti scioglierti la faccia. Sei stato avvisato!

un'eccezione:

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

Fondamentalmente stai riformulando la domanda senza il punto interrogativo. ha detto * sa che esistono algoritmi 'std' più appropriati e ha menzionato la possibilità di lanciare un'eccezione – jalf

+1

-1 jalf ha detto – IronMensan

2

lambda sono il livello di astrazione sbagliata perché si comportano in gran parte come funzioni - almeno quando si tratta di controllare il flusso, che è ciò che conta qui. Non si desidera qualcosa come "incapsulato" come una funzione (o le procedure di programmazione procedurale), che possono in C++ solo un ritorno diretto o un'eccezione. A mio avviso, qualsiasi tentativo di sovvertire questo comportamento dovrebbe essere considerato patologico - o almeno non dovrebbe mascherarsi come una procedura.

Per un controllo più fine del flusso di esecuzione, qualcosa come le coroutine potrebbe essere un livello di astrazione e/o primitiva più adatto. Tuttavia, temo che il risultato finale non assomigli all'utilizzo di std::for_each.

2

usare un algoritmo personalizzato:

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, questo è in realtà std :: all_of ma si ottiene l'idea. (Vedi la "risposta di riduzione"). Se la funzione deve restituire un certo tipo, si potrebbe desiderare di usare un certo tipo di 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 { 
    … 
}; 

vedere i tipi Haskell corrispondenti. Potresti usare C++ 01 "Unrestricted Union" per implementarlo in modo pulito.

Un modo pulito per eseguire l'uscita non locale, utilizza le continuazioni ma non le si dispone in C++.

0

Come voi e altri avete sottolineato for_each non è l'algoritmo giusto da usare qui. Non c'è modo di uscire dal ciclo for_each - tranne l'eccezione (gioco di parole): devi eseguirlo 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

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