2015-07-08 5 views
5

Po przypisaniu lambda do jawnie wpisanej zmiennej (na przykład, gdy jest rekursywna, aby uchwycić samą funkcję), używam std::function.Czy istnieje typ std :: function lub podobny dla lambda z parametrem auto?

Rozważmy tę głupią „bit licząc” funkcję jako przykład:

std::function<int(int)> f; 
f = [&f](int x){ return x ? f(x/2)+1 : 0; }; 

Co o przypadku, gdy używamy parametru auto uogólniać x, który został wprowadzony w C++ 14 generycznego lambda?

std::function<int(???)> f; 
f = [&f](auto x){ return x ? f(x/2)+1 : 0; }; 

Oczywiście, nie mogę umieścić auto parametrów wpisać function.

Czy istnieje możliwość zdefiniowania klasy funktora na tyle ogólnie, aby pokryć dokładny przypadek powyżej, ale nadal używając lambda do definicji funkcji?

(Nie generalizuj zbytnio tego, akceptuj tylko jeden parametr automatyczny i zakodowany kod wartości zwracanej.) Przypadek użycia byłby taki jak w powyższym scenariuszu: przechwytywanie samej funkcji przez odwołanie do wywołań rekursywnych.

+0

Problem tutaj, jak sądzę, polega na tym, że funkcja lamba wymaga określonego typu - "auto" po prostu pozwala na samodzielną kompilację. Nie oznacza to, że można przekazać funkcję lambda do dowolnego kodu, który ma dowolny typ - typ musi być znany podczas kompilacji funkcji lambda. –

+1

Co z 'auto & f = [& f] (auto x) {return x? f (x/2) +1: 0;}; '? Czy to działa? (Prawdopodobnie po prostu 'auto' zamiast' auto & '.) Nie mam przy sobie kompilatora obsługującego C++ 14, więc nie mogę go łatwo przetestować. – celticminstrel

+0

'std :: function' używa typu wymazania. Jestem dość pewny, że * zasadniczo niemożliwe * jest napisanie owijanego obiektu funkcji typu, który akceptuje dowolne typy, ponieważ każdy zestaw typów argumentów musi * stworzyć * nową funkcję, która może być wykonana tylko wtedy, gdy kompilator widzi owinięty * szablon funkcji *. Obejścia obejmują użycie stałego zestawu zestawów typów parametrów (użyj istniejących zestawów przeciążeń/wykonaj wstępne obliczenia wszystkich przeciążeń przed usunięciem typu) lub usuń argumenty typu. – dyp

Odpowiedz

4

Można utworzyć lambda, który wywołuje się poprzez przepuszczenie go sobie jako parametr:

auto f = [](auto self, auto x) -> int { 
    return x ? self(self, x/2) + 1 : 0; 
}; 

std::cout << f(f, 10); 

Następnie można uchwycić w innym lambda lambda, więc nie trzeba się martwić o przepuszczenie go do sam:

auto f2 = [&f](auto x) { 
    return f(f, x); 
}; 

std::cout << f2(10); 
+0

Mimo że nie odpowiadasz na faktyczne pytanie (chciałem wpisać nazwę typu dla funktora), myślę, że mogę go użyć do rozwiązania mojego rzeczywistego problemu (który jest zbyt długi, aby wyjaśnić - myślałem, że muszę jawnie podać functor typu, ale myślę, że mogę to obejść za pomocą twojego rozwiązania w moim konkretnym scenariuszu). – leemes

3

Oto szybki y-syntezator oparty rekurencyjne silnika:

template<class F> 
struct recursive_t { 
    F f; 

    // note Self must be an lvalue reference. Things get 
    // strange if it is an rvalue: 
    // invoke makes recursive ADL work a touch better. 
    template<class Self, class...Args> 
    friend auto invoke(Self& self, Args&&...args) 
    -> decltype(self.f(self, std::declval<Args>()...)) 
    { 
    return self.f(self, std::forward<Args>(args)...); 
    } 
    // calculate return type using `invoke` above: 
    template<class Self, class...Args> 
    using R = decltype(invoke(std::declval<Self>(), std::declval<Args>()...)); 

    template<class...Args> 
    R<recursive_t&, Args...> operator()(Args&&...args) 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
    template<class...Args> 
    R<recursive_t const&, Args...> operator()(Args&&...args)const 
    { 
    return invoke(*this, std::forward<Args>(args)...); 
    } 
}; 

template<class F> 
recursive_t< std::decay_t<F> > recurse(F&& f) 
{ 
    return {std::forward<F>(f)}; 
} 

Teraz można zrobić:

auto f = recurse([](auto&& f, auto x){ return x ? f(x/2)+1 : 0; }); 

i masz rekurencyjne lambda, która nie ma przechwytywania & (co ogranicza jego zastosowanie do bieżącego zakresu).

Przechwytywanie std::function przez odniesienie oznacza, że ​​czas życia lambda jest bieżącym zakresem, a każde wywołanie rekurencyjne wymaga przejścia przez usuwanie typu (blokowanie dowolnej możliwej optymalizacji, takiej jak rekursja ogona, przez rekursywne wywołanie). To samo dotyczy innych podobnych rozwiązań.

Używanie recursive_t jest wymagane, zamiast używać lambda, ponieważ lambda nie może sama się w sobie nazwać.

Live example.

Wersja oparta na lambdzie jest nieco prostsza w implementacji. Zauważ, że należałoby inną funkcję typu dla modyfikowalnych i niezmiennych lambdas:

template<class F> 
auto recurse(F&& f) { 
    return [f=std::forward<F>(f)](auto&&...args){ 
    return f(f, decltype(args)(args)...); 
    }; 
}; 

W recursive_t działa jak:

auto fib = recurse([](auto&& fib, int x){ if (x<2) return 1; return fib(x-1)+fib(x-2); }); 

wersja lambda działa jak:

auto fib = recurse([](auto&& self, int x){ if (x<2) return 1; return self(self, x-1)+self(self,x-2); }); 

które , osobiście, znajdź coś bardziej niezręcznego.

Trudniej jest również opisać typ recurse. Dla wersji recursive_t, recurse jest typu:

((A->B)->A->B)->(A->B) 

co jest niewygodne, ale to typ skończony.

Wersja lambda jest trudniejsza. Typ argumentu funkcyjnym recursive jest typu:

F:= F->A->B 

który nieznośno nieskończona, a następnie recurse jest typu

F->A->(A->B) 

która dziedziczy się w nieskończoność.

W każdym razie wartość zwracana recurse może być przechowywana w przyziemnym std::function lub nie jest przechowywana w żadnym wymazanym typie pojemniku.

Powiązane problemy