2014-09-23 13 views
8

tworzę lambda tak:„Odśwież” obiekt lambda

int i = 0; 
auto gen_lam = [=]() mutable -> int {return ++i;}; 

Skutecznie zlicza ile razy została nazwana, ponieważ przechowuje przechwycony i. Czy istnieje sposób "rekonstrukcji" obiektu, aby zaczął się on od wartości początkowej i?

Coś wzdłuż linii:

decltype(gen_lam) gen_lam2; 

takie, że następujące wyjścia kod 1 1 zamiast 1 2

std::cout << gen_lam() << std::endl; 

decltype(gen_lam) gen_lam2; 

std::cout << gen_lam2() << std::endl; 
+3

Dla prostoty i rozsądku, utworzę dla tego klasę zwykłego starego funktora. –

+0

Chciałbym zapisać go w 'std :: function' i nienawidziłbym narzucać innego typu na osobę dzwoniącą/użytkownika mojej klasy – TeaOverflow

+0

@TeaOverflow: Tak długo, jak dodatkowy narzut do usuwania typu jest akceptowalny ... – Deduplicator

Odpowiedz

17

Wystarczy zrobić, owinąć lambda-utworzenie w lambda, który można nazwać za każdym razem, gdy potrzebna jest wewnętrzna inicjacja lambda:

auto wrap_lam = [](int i) {return [=]() mutable {return ++i;};} 
auto gen_lam = wrap_lam(0); 
auto gen_lam2 = wrap_lam(0); 

Albo po prostu zrobić kopię zachować stan, kiedy tylko chcesz:

auto gen_lam = [=]() mutable -> int {return ++i;}; 
const auto save = gen_lam; 

Jeśli tylko kiedykolwiek chcą pełnego resetu, naturalnie zapisać jako pierwszy obiekt const.

+4

Możesz nawet napisać 'auto const prototype = [=]() mutable {return ++ i; }; 'więc wiesz, że prototyp sam w sobie nie jest przypadkowo zwiększany. – MSalters

+0

Miło ... Nawet nie pomyślałem o przechwyceniu licznika wartości przejazdu w generatorze. – IdeaHat

+0

@MSalters: Chciałem tylko podkreślić, że można go zapisać w dowolnym momencie, nie tylko przy tworzeniu. Oczywiście, jeśli pożądany jest tylko pełny reset, twój jest lepszy z powodu błędów logicznych składni błędów, na które kompilator będzie narzekał. – Deduplicator

1

Nie sądzę, że wartości początkowe, które zostały przechwycone przez lambda, mogą być częścią tego typu, ponieważ oznaczałoby to, że za każdym razem, gdy kod ten jest uruchamiany (z możliwymi różnicami wartości i), musiałby być nowy typ wygenerowane w czasie wykonywania.

Nie sądzę również, że stan początkowy przechwytywanych wartości zmiennych jest przechowywany, ponieważ zajmie to dodatkową pamięć.

Podsumowując, jeśli wszystko, co masz, to obiekt funkcji lambda, który został stworzony w sposób, w jaki opisujesz, nie sądzę, abyś miał jakikolwiek sposób odtworzenia swojego pierwotnego stanu.

Jednakże, jeśli kontrolujesz kod, który generuje obiekt funkcji, możesz go przeciągnąć do funkcji i ponownie wywołać tę funkcję. Lub możesz ręcznie utworzyć klasę obiektu funkcji, jak już zasugerował @NeilKirk.

+0

Początkowa wartość przechwyconej zmiennej nie musi być zapisywana, ponieważ pierwotne 'i' jest przechwytywane przez wartość i dlatego nie" dotknięte " – TeaOverflow

+0

Prawda, ale oryginalne' i' mogło już dawno wykroczyć poza zakres. W twoim pytaniu nie było to zbyt jasne, DLACZEGO chciałeś odświeżyć/zresetować obiekt lambda, więc próbowałem rozwiązać problem dosłownie i przy minimalnych założeniach - podając * tylko * obiekt lambda, spróbuj go zresetować lub zrobić " odświeżony "copy. – Medo42

+0

W twoim kodzie pojawią się dwie wersje 'i': Jedna jest zmienną lokalną (która pozostaje niezmieniona), a druga jest kopią wartości w lambda. Ale ta kopia będzie modyfikowana za każdym razem, gdy wywołasz lambdę, więc powinieneś powrócić do oryginalnego 'i', aby uzyskać wartość początkową, nie możesz tego uzyskać, wyszukując * tylko * w samym obiekcie lambda. – Medo42

2

Myślę, że to, co chcesz, może być wykonane przez generator, który przechwytuje różne liczniki.

#include <iostream> 
#include <functional> 

using namespace std; 

int main() 
{ 
    int i = 0; 
    int j = 0; 

    auto lambda_generator = [](int& val) { 
     auto var = [=]() mutable -> int {return ++val;}; 
     return var; 
    }; 

    auto counter1 = lambda_generator(i); 

    std::cout << counter1() << std::endl; 

    auto counter2 = lambda_generator(j); 

    std::cout << counter2() << std::endl; 

} 

Jeśli "reset" oznacza, że ​​chcesz mieć inny licznik.

+0

Udostępnia mi stan współdzielony drugiego przykładu. Rozwiązanie Deduplicator, w którym każda lambda ma swój własny stan, jest o wiele ładniejsze. –

+0

@SebastianRedl Zgadzam się ... nawet nie myślałem o przechwytywaniu skopiowanej liczby całkowitej typu wartość po przejściu. Zostawię tutaj swoją głupią odpowiedź dla celów pedagogicznych. – IdeaHat

+0

@MSalters Całkowicie usunę drugi przykład – IdeaHat

2

Nie można uzyskać dostępu do wewnętrznych zmiennych skopiowanych w lambda, ale sama lambda może.

Poprzez ustanowienie protokołu przez połączenie można wysyłać polecenia do lambda, aby zmutować jego stan. Na przykład:

auto counter = [](int i0){ 
    int i = i0; 
    return [i0, i](bool reset=false) mutable { 
     if (reset) { 
      i = i0; 
      return -1; 
     } else { 
      return ++i; 
     } 
    }; 
}; 

Tutaj używam opcjonalny parametr, który po przejściu true będzie zresetowanie licznika do wartości początkowej.

int main() { 
    auto c = counter(10); 
    std::cout << c() << "\n"; // 11 
    std::cout << c() << "\n"; // 12 
    std::cout << c() << "\n"; // 13 
    c(true); // send reset message 
    std::cout << c() << "\n"; // 11 
    std::cout << c() << "\n"; // 12 
    std::cout << c() << "\n"; // 13 
}